Create route for Opinions
create OpinionRepository create OpinionVoter create OpinionChoiceRef create extention String.toUUID() and List<String>.toUUID() create OpinionAggregation create interface RequestBuilderWithCreator for create entity by request rename opinion_list to opinion_choice create sql function find_citizen_opinions fix sql function find_citizen_opinions_by_target_id fix sql funciton find_opinion_choices
This commit is contained in:
4
.idea/runConfigurations/Test_All_SQL.xml
generated
4
.idea/runConfigurations/Test_All_SQL.xml
generated
@@ -49,10 +49,12 @@
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/vote/find_citizen_votes_by_target_ids.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/vote/find_votes_by_citizen.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/count_opinion.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_citizen_opinions.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_citizen_opinions_by_target_id.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_citizen_opinions_by_target_ids.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_opinion_choice_by_id.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_opinion_choices.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/opinion.sql" />
|
||||
<option value="$PROJECT_DIR$/src/main/resources/sql/functions/opinion/find_opinions.sql" />
|
||||
<option value="$PROJECT_DIR$/src/test/sql/user.sql" />
|
||||
<option value="$PROJECT_DIR$/src/test/sql/citizen.sql" />
|
||||
<option value="$PROJECT_DIR$/src/test/sql/article.sql" />
|
||||
|
||||
@@ -143,6 +143,7 @@ fun Application.module(env: Env = PROD) {
|
||||
CommentVoter(),
|
||||
VoteVoter(),
|
||||
FollowVoter(),
|
||||
OpinionVoter(),
|
||||
OpinionChoiceVoter()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import fr.dcproject.repository.CommentGeneric as CommentGenericRepository
|
||||
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||
import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
|
||||
import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository
|
||||
import fr.dcproject.repository.User as UserRepository
|
||||
import fr.dcproject.repository.VoteArticle as VoteArticleRepository
|
||||
@@ -59,6 +60,7 @@ val Module = module {
|
||||
single { VoteConstitutionRepository(get()) }
|
||||
single { VoteCommentRepository(get()) }
|
||||
single { OpinionChoiceRepository(get()) }
|
||||
single { OpinionArticleRepository(get()) }
|
||||
|
||||
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
||||
|
||||
|
||||
@@ -6,9 +6,14 @@ import java.util.*
|
||||
open class Opinion<T : TargetI>(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val createdBy: CitizenBasic,
|
||||
override var target: T,
|
||||
var name: String
|
||||
override val target: T,
|
||||
val choice: OpinionChoice
|
||||
) : ExtraI<T>,
|
||||
UuidEntity(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy)
|
||||
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy) {
|
||||
|
||||
fun getName(): String = choice.name
|
||||
}
|
||||
|
||||
typealias OpinionArticle = Opinion<Article>
|
||||
@@ -11,6 +11,10 @@ class OpinionChoice(
|
||||
id: UUID,
|
||||
val name: String,
|
||||
val target: List<String>
|
||||
) : UuidEntity(id),
|
||||
) : OpinionChoiceRef(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityDeletedAt by EntityDeletedAtImp()
|
||||
EntityDeletedAt by EntityDeletedAtImp()
|
||||
|
||||
open class OpinionChoiceRef(
|
||||
id: UUID
|
||||
) : UuidEntity(id)
|
||||
@@ -1,5 +1,11 @@
|
||||
package fr.dcproject.entity
|
||||
|
||||
import fr.postgresjson.entity.EntityI
|
||||
|
||||
class OpinionAggregation(
|
||||
override val entries: Set<Map.Entry<String, Int>> = emptySet()
|
||||
) : AbstractMap<String, Int>(), EntityI
|
||||
|
||||
interface Opinionable {
|
||||
val opinions: MutableMap<String, Int>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.dcproject.entity.request
|
||||
|
||||
import fr.dcproject.entity.Citizen
|
||||
import fr.dcproject.entity.OpinionArticle
|
||||
import fr.dcproject.entity.OpinionChoiceRef
|
||||
import fr.dcproject.entity.TargetRef
|
||||
import fr.dcproject.repository.Article
|
||||
import fr.dcproject.repository.OpinionChoice
|
||||
import fr.dcproject.utils.toUUID
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
|
||||
class ArticleOpinionRequest(
|
||||
target: String,
|
||||
opinionChoice: String
|
||||
) : RequestBuilderWithCreator<Citizen, OpinionArticle>, KoinComponent {
|
||||
val target = TargetRef(target.toUUID())
|
||||
val opinionChoice = OpinionChoiceRef(opinionChoice.toUUID())
|
||||
|
||||
override fun create(citizen: Citizen): OpinionArticle {
|
||||
return OpinionArticle(
|
||||
choice = get<OpinionChoice>().findOpinionChoiceById(opinionChoice.id)!!,
|
||||
target = get<Article>().findById(target.id)!!,
|
||||
createdBy = citizen
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,14 @@
|
||||
package fr.dcproject.entity.request
|
||||
|
||||
interface Request
|
||||
import fr.dcproject.entity.CitizenRef
|
||||
import fr.postgresjson.entity.EntityI
|
||||
|
||||
interface Request
|
||||
|
||||
interface RequestBuilder<E: EntityI> : Request {
|
||||
fun create(): E
|
||||
}
|
||||
|
||||
interface RequestBuilderWithCreator<C: CitizenRef, E: EntityI> : Request {
|
||||
fun create(citizen: C): E
|
||||
}
|
||||
|
||||
112
src/main/kotlin/fr/dcproject/repository/Opinion.kt
Normal file
112
src/main/kotlin/fr/dcproject/repository/Opinion.kt
Normal file
@@ -0,0 +1,112 @@
|
||||
package fr.dcproject.repository
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import fr.dcproject.entity.Article
|
||||
import fr.dcproject.entity.OpinionAggregation
|
||||
import fr.dcproject.entity.TargetRef
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import net.pearx.kasechange.toSnakeCase
|
||||
import java.util.*
|
||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||
import fr.dcproject.entity.Opinion as OpinionEntity
|
||||
import fr.dcproject.entity.OpinionChoice as OpinionChoiceEntity
|
||||
|
||||
open class OpinionChoice(override val requester: Requester) : RepositoryI {
|
||||
/**
|
||||
* find all opinion choices
|
||||
* can be filtered by target compatibility
|
||||
*/
|
||||
fun findOpinionsChoices(targets: List<String> = emptyList()): List<OpinionChoiceEntity> =
|
||||
requester
|
||||
.getFunction("find_opinion_choices")
|
||||
.select(
|
||||
"targets" to targets
|
||||
)
|
||||
|
||||
/**
|
||||
* find one opinion choices by id
|
||||
*/
|
||||
fun findOpinionChoiceById(id: UUID): OpinionChoiceEntity? =
|
||||
requester
|
||||
.getFunction("find_opinion_choice_by_id")
|
||||
.selectOne(
|
||||
"id" to id
|
||||
)
|
||||
}
|
||||
|
||||
open class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requester) {
|
||||
/**
|
||||
* Create an Opinion on target (article,...)
|
||||
*/
|
||||
fun opinion(opinion: OpinionEntity<T>): OpinionAggregation {
|
||||
return requester
|
||||
.getFunction("opinion")
|
||||
.selectOne(
|
||||
"reference" to opinion.target.reference,
|
||||
"target_id" to opinion.target.id,
|
||||
"opinion" to opinion.id,
|
||||
"created_by_id" to opinion.createdBy
|
||||
)!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Find opinions of one citizen filtered by target ids
|
||||
*/
|
||||
fun findCitizenOpinionsByTargets(
|
||||
citizen: CitizenEntity,
|
||||
targets: List<UUID>
|
||||
): List<OpinionEntity<T>> {
|
||||
val typeReference = object : TypeReference<List<OpinionEntity<T>>>() {}
|
||||
return requester.run {
|
||||
getFunction("find_citizen_opinions_by_target_ids")
|
||||
.select(
|
||||
typeReference, mapOf(
|
||||
"citizen_id" to citizen.id,
|
||||
"ids" to targets
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find opinion of citizen filtered by one target id
|
||||
*/
|
||||
fun findCitizenOpinionsByTarget(
|
||||
citizen: CitizenEntity,
|
||||
target: UUID
|
||||
): List<OpinionEntity<T>> {
|
||||
val typeReference = object : TypeReference<List<OpinionEntity<T>>>() {}
|
||||
return requester
|
||||
.getFunction("find_citizen_opinions_by_target_id")
|
||||
.select(
|
||||
typeReference, mapOf(
|
||||
"citizen_id" to citizen.id,
|
||||
"id" to target
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* find paginated opinion of one citizen
|
||||
* can be sorted
|
||||
*/
|
||||
fun findCitizenOpinions(
|
||||
citizen: CitizenEntity,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: String? = null,
|
||||
direction: RepositoryI.Direction? = null
|
||||
): Paginated<OpinionEntity<TargetRef>> {
|
||||
return requester
|
||||
.getFunction("find_citizen_opinions")
|
||||
.select(page, limit,
|
||||
"sort" to sort?.toSnakeCase(),
|
||||
"direction" to direction,
|
||||
"citizen" to citizen.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class OpinionArticle(requester: Requester) : Opinion<Article>(requester)
|
||||
72
src/main/kotlin/fr/dcproject/routes/OpinionArticle.kt
Normal file
72
src/main/kotlin/fr/dcproject/routes/OpinionArticle.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.entity.request.ArticleOpinionRequest
|
||||
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import fr.dcproject.utils.toUUID
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.locations.put
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
import fr.dcproject.entity.Article as ArticleEntity
|
||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||
import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object OpinionArticlePaths {
|
||||
/**
|
||||
* Get paginated opinion of citizen for one article
|
||||
*/
|
||||
@Location("/citizens/{citizen}/opinions/articles")
|
||||
class CitizenOpinionArticleRequest(
|
||||
val citizen: CitizenEntity,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
/**
|
||||
* Put an opinion on one article
|
||||
*/
|
||||
@Location("/articles/{article}/opinons")
|
||||
class ArticleOpinion(val article: ArticleEntity)
|
||||
|
||||
/**
|
||||
* Get all Opinion of citizen on targets by target ids
|
||||
*/
|
||||
@Location("/citizen/{citizen}/opinions")
|
||||
class CitizenOpinions(val citizen: CitizenEntity, id: List<String>): KoinComponent {
|
||||
val opinionsEntities = get<OpinionArticleRepository>()
|
||||
.findCitizenOpinionsByTargets(citizen, id.toUUID())
|
||||
}
|
||||
}
|
||||
|
||||
@KtorExperimentalAPI
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.opinionArticle(repo: OpinionArticleRepository) {
|
||||
get<OpinionArticlePaths.CitizenOpinionArticleRequest> {
|
||||
val opinions = repo.findCitizenOpinions(citizen, it.page, it.limit)
|
||||
call.respond(opinions)
|
||||
}
|
||||
|
||||
get<OpinionArticlePaths.CitizenOpinions> {
|
||||
assertCan(VIEW, it.opinionsEntities)
|
||||
|
||||
call.respond(it.opinionsEntities)
|
||||
}
|
||||
|
||||
put<OpinionArticlePaths.ArticleOpinion> {
|
||||
val optionArticle = call.receive<ArticleOpinionRequest>().create(citizen)
|
||||
assertCan(VIEW, optionArticle)
|
||||
|
||||
call.respond(HttpStatusCode.Created, optionArticle)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import fr.dcproject.routes.VoteArticlePaths.CommentVoteRequest
|
||||
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
|
||||
import fr.dcproject.security.voter.VoteVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import fr.dcproject.utils.toUUID
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -45,10 +46,7 @@ object VoteArticlePaths {
|
||||
|
||||
@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) }
|
||||
val id: List<UUID> = id.toUUID()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
src/main/kotlin/fr/dcproject/security/voter/OpinionVoter.kt
Normal file
53
src/main/kotlin/fr/dcproject/security/voter/OpinionVoter.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package fr.dcproject.security.voter
|
||||
|
||||
import fr.dcproject.entity.Opinion
|
||||
import io.ktor.application.ApplicationCall
|
||||
|
||||
class OpinionVoter : Voter {
|
||||
enum class Action : ActionI {
|
||||
CREATE,
|
||||
VIEW,
|
||||
DELETE
|
||||
}
|
||||
|
||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||
return (action is Action)
|
||||
.and(subject is Opinion<*>? || subject is List<*>)
|
||||
}
|
||||
|
||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||
val user = call.user
|
||||
if (action == Action.CREATE) {
|
||||
return if (user != null) Vote.GRANTED else Vote.DENIED
|
||||
}
|
||||
|
||||
if (action == Action.VIEW) {
|
||||
if (subject is Opinion<*>) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
if (subject is List<*>) {
|
||||
subject.forEach {
|
||||
if (it !is Opinion<*>) {
|
||||
return Vote.DENIED
|
||||
}
|
||||
}
|
||||
return Vote.GRANTED
|
||||
}
|
||||
return Vote.DENIED
|
||||
}
|
||||
|
||||
if (action == Action.DELETE) {
|
||||
return if (subject is Opinion<*>
|
||||
&& user != null
|
||||
&& subject.createdBy.user.id == user.id)
|
||||
Vote.GRANTED
|
||||
else Vote.DENIED
|
||||
}
|
||||
|
||||
if (action is Action) {
|
||||
return Vote.DENIED
|
||||
}
|
||||
|
||||
return Vote.ABSTAIN
|
||||
}
|
||||
}
|
||||
10
src/main/kotlin/fr/dcproject/utils/Uuid.kt
Normal file
10
src/main/kotlin/fr/dcproject/utils/Uuid.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package fr.dcproject.utils
|
||||
|
||||
import java.util.*
|
||||
|
||||
fun String.toUUID(): UUID = UUID.fromString(this.trim())
|
||||
|
||||
fun List<String>.toUUID(): List<UUID> = this
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.map { UUID.fromString(it) }
|
||||
@@ -2,22 +2,22 @@ do
|
||||
$$
|
||||
declare
|
||||
article_count int = (select count(*) from article);
|
||||
_citizensIds uuid[] = (select array_agg(id) from citizen);
|
||||
begin
|
||||
delete from opinion_on_article;
|
||||
delete from opinion_list;
|
||||
delete from opinion_choice;
|
||||
|
||||
insert into opinion_list (id, name, target)
|
||||
select uuid_in(md5('opinion_list'||row_number() over ())::cstring), 'Opinion'||row_number() over (), '{article}'
|
||||
insert into opinion_choice (id, name, target)
|
||||
select uuid_in(md5('opinion_choice'||row_number() over ())::cstring), 'Opinion'||row_number() over (), '{article}'
|
||||
from generate_series(0,20);
|
||||
|
||||
for i in 0..9 loop
|
||||
insert into opinion_on_article (id, created_by_id, target_id, opinion)
|
||||
insert into opinion_on_article (id, created_by_id, target_id, choice_id, created_at)
|
||||
select
|
||||
uuid_in(md5('opinion_on_article'||rn+(article_count*i))::cstring),
|
||||
z.id,
|
||||
a.id,
|
||||
uuid_in(md5('opinion_list'||((rn+i) % 5 +1))::cstring)
|
||||
uuid_in(md5('opinion_choice'||((rn+i) % 5 +1))::cstring),
|
||||
now() + ((rn+i) || ' minute')::interval
|
||||
from (select *, row_number() over ()+i+5 % 5 rn from citizen) z
|
||||
join (select *, row_number() over () rn from article) a using (rn);
|
||||
end loop;
|
||||
|
||||
@@ -9,10 +9,10 @@ begin
|
||||
into agg
|
||||
from (
|
||||
select
|
||||
count(o.opinion) as total,
|
||||
count(o) as total,
|
||||
ol.name as label
|
||||
from opinion o
|
||||
join opinion_list ol on o.opinion = ol.id
|
||||
join opinion_choice ol on o.choice_id = ol.id
|
||||
where o.target_id = _target_id
|
||||
group by ol.name
|
||||
order by ol.name
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
create or replace function find_citizen_opinions(
|
||||
_citizen_id uuid,
|
||||
direction text default 'desc',
|
||||
sort text default 'created_at',
|
||||
"limit" int default 50,
|
||||
"offset" int default 0,
|
||||
out resource json,
|
||||
out total int
|
||||
) language plpgsql as
|
||||
$$
|
||||
begin
|
||||
select json_agg(t), (
|
||||
select count(o.id)
|
||||
from opinion o
|
||||
where o.created_by_id = _citizen_id
|
||||
)
|
||||
into resource, total
|
||||
from (
|
||||
select
|
||||
o.*,
|
||||
to_json(ol) as choice
|
||||
from opinion as o
|
||||
join opinion_choice ol on o.choice_id = ol.id
|
||||
|
||||
where created_by_id = _citizen_id
|
||||
|
||||
order by
|
||||
case direction when 'asc' then
|
||||
case sort
|
||||
when 'created_at' then o.created_at::text
|
||||
else null
|
||||
end
|
||||
end,
|
||||
case direction when 'desc' then
|
||||
case sort
|
||||
when 'created_at' then o.created_at::text
|
||||
end
|
||||
end
|
||||
desc,
|
||||
o.created_at desc
|
||||
limit "limit" offset "offset"
|
||||
) t;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- select * from find_citizen_opinions('6434f4f9-f570-f22a-c134-8668350651ff', null, null, 2, 2);
|
||||
@@ -11,9 +11,9 @@ begin
|
||||
from (
|
||||
select
|
||||
o.*,
|
||||
ol.name
|
||||
to_json(ol) as choice
|
||||
from opinion as o
|
||||
join opinion_list ol on o.opinion = ol.id
|
||||
join opinion_choice ol on o.choice_id = ol.id
|
||||
|
||||
where target_id = _id
|
||||
and created_by_id = _citizen_id
|
||||
|
||||
@@ -9,9 +9,9 @@ begin
|
||||
from (
|
||||
select
|
||||
o.*,
|
||||
ol.name
|
||||
to_json(ol) as choice
|
||||
from opinion as o
|
||||
join opinion_list ol on o.opinion = ol.id
|
||||
join opinion_choice ol on o.choice_id = ol.id
|
||||
|
||||
where target_id = any(_ids)
|
||||
and created_by_id = _citizen_id
|
||||
|
||||
@@ -3,7 +3,7 @@ create or replace function find_opinion_choice_by_id(_id uuid, out resource json
|
||||
$$
|
||||
begin
|
||||
select to_json(ol) into resource
|
||||
from opinion_list ol
|
||||
from opinion_choice ol
|
||||
where (ol.deleted_at <= now()
|
||||
or ol.deleted_at is null)
|
||||
and (ol.id = _id);
|
||||
|
||||
@@ -6,10 +6,9 @@ begin
|
||||
into resource
|
||||
from (
|
||||
select ol.*
|
||||
from opinion_list ol
|
||||
where (ol.deleted_at <= now()
|
||||
or ol.deleted_at is null)
|
||||
and (ol.target is null or array_length(targets) = 0 or ol.target = any(targets))
|
||||
from opinion_choice ol
|
||||
where (ol.deleted_at is null or ol.deleted_at > now())
|
||||
and (ol.target is null or targets is null or array_length(targets, 1) = 0 or ol.target && targets)
|
||||
|
||||
order by ol.name
|
||||
) t;
|
||||
@@ -3,9 +3,9 @@ create or replace function opinion(reference regclass, _target_id uuid, _created
|
||||
$$
|
||||
begin
|
||||
if reference = 'article'::regclass then
|
||||
insert into opinion_on_article (created_by_id, target_id, opinion)
|
||||
insert into opinion_on_article (created_by_id, target_id, choice_id)
|
||||
values (_created_by_id, _target_id, _opinion)
|
||||
on conflict (created_by_id, target_id, opinion) do nothing;
|
||||
on conflict (created_by_id, target_id, choice_id) do nothing;
|
||||
else
|
||||
raise exception '% no implemented for opinion', reference::text;
|
||||
end if;
|
||||
|
||||
@@ -4,7 +4,7 @@ drop table if exists resource_view;
|
||||
-- Extra resources
|
||||
drop table if exists opinion_on_article;
|
||||
drop table if exists opinion;
|
||||
drop table if exists opinion_list;
|
||||
drop table if exists opinion_choice;
|
||||
|
||||
drop table if exists follow_article;
|
||||
drop table if exists follow_constitution;
|
||||
|
||||
@@ -503,7 +503,7 @@ create table resource_view
|
||||
ip cidr null
|
||||
);
|
||||
|
||||
create table opinion_list
|
||||
create table opinion_choice
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
name text not null unique,
|
||||
@@ -514,20 +514,20 @@ create table opinion_list
|
||||
|
||||
create table opinion
|
||||
(
|
||||
opinion uuid not null references opinion_list (id),
|
||||
choice_id uuid not null references opinion_choice (id),
|
||||
foreign key (created_by_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (created_by_id, target_id, opinion)
|
||||
unique (created_by_id, target_id, choice_id)
|
||||
) inherits (extra);
|
||||
|
||||
create table opinion_on_article
|
||||
(
|
||||
target_reference regclass default 'article'::regclass not null,
|
||||
foreign key (opinion) references opinion_list (id),
|
||||
foreign key (choice_id) references opinion_choice (id),
|
||||
foreign key (target_id) references article (id),
|
||||
foreign key (created_by_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (created_by_id, target_id, opinion)
|
||||
unique (created_by_id, target_id, choice_id)
|
||||
) inherits (opinion);
|
||||
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ begin
|
||||
select upsert_article(created_article) into created_article;
|
||||
|
||||
|
||||
insert into opinion_list(id, name, target)
|
||||
insert into opinion_choice(id, name, target)
|
||||
values (opinion1, 'Opinion1', '{article}');
|
||||
|
||||
insert into opinion_list(id, name, target)
|
||||
insert into opinion_choice(id, name, target)
|
||||
values (opinion2, 'Opinion2', '{article}');
|
||||
|
||||
insert into opinion_list(name, target)
|
||||
insert into opinion_choice(name, target)
|
||||
values ('Opinion3', '{article}');
|
||||
|
||||
perform opinion(
|
||||
@@ -73,33 +73,51 @@ begin
|
||||
_created_by_id => _citizen_id,
|
||||
_opinion => opinion1
|
||||
);
|
||||
assert (select count(*) = 1 from opinion_on_article), 'opinion must be inserted';
|
||||
assert (select opinion = opinion1 from opinion_on_article limit 1), 'opinion must be inserted';
|
||||
perform opinion(
|
||||
reference => 'article'::regclass,
|
||||
_target_id => (created_article->>'id')::uuid,
|
||||
_created_by_id => _citizen_id,
|
||||
_opinion => opinion2
|
||||
);
|
||||
assert (select count(*) = 2 from opinion_on_article), 'opinions must be inserted';
|
||||
assert (select choice_id = opinion1 from opinion_on_article limit 1), 'opinion must be inserted';
|
||||
|
||||
assert(select (a#>>'{opinions, Opinion1}')::int = 1
|
||||
from find_article_by_id((created_article->>'id')::uuid) a), 'the article must be have a opinion';
|
||||
|
||||
assert(
|
||||
select (o#>>'{0, name}') = 'Opinion1'
|
||||
select (o#>>'{0, choice, name}') = 'Opinion1'
|
||||
from find_citizen_opinions_by_target_id(_citizen_id, (created_article->>'id')::uuid) o),
|
||||
'The opinion must have a name';
|
||||
|
||||
assert(
|
||||
select (o#>>'{0, name}') = 'Opinion1'
|
||||
select (o#>>'{0, choice, name}') = 'Opinion1'
|
||||
from find_citizen_opinions_by_target_ids(_citizen_id, array[(created_article->>'id')::uuid]) o),
|
||||
'The first opinion must have a name';
|
||||
|
||||
assert(
|
||||
select find_opinion_choices()#>>'{0, name}' = 'Opinion1'
|
||||
), 'find_opinion_choices mst be return all opinions';
|
||||
), 'find_opinion_choices must be return all opinions';
|
||||
|
||||
assert(
|
||||
select (find_opinion_choice_by_id(opinion1)->>'name') = 'Opinion1'
|
||||
), 'find_opinion_choice_by_id must return the opinion_choice';
|
||||
|
||||
assert(
|
||||
select json_array_length(resource) = 1 from find_citizen_opinions(_citizen_id, null, null, 1, 1)
|
||||
), 'find_citizen_opinions must return only 1 result if limit is set to 1';
|
||||
|
||||
assert(
|
||||
select total = 2 from find_citizen_opinions(_citizen_id, null, null, 2, 1)
|
||||
), 'find_citizen_opinions must return the total and it should be 2';
|
||||
|
||||
assert(
|
||||
select (resource#>>'{0, choice, name}') = 'Opinion1' from find_citizen_opinions(_citizen_id, null, null, 1, 0)
|
||||
), 'find_citizen_opinions must return a list of opinion with name';
|
||||
|
||||
-- delete vote and context
|
||||
delete from opinion;
|
||||
delete from opinion_list;
|
||||
delete from opinion_choice;
|
||||
delete from article;
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
Reference in New Issue
Block a user