fixs and move files
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
package fr.dcproject
|
||||
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.dsl.module
|
||||
import java.io.File
|
||||
import fr.dcproject.repository.Article as ArticleRepository
|
||||
|
||||
val config = Config()
|
||||
|
||||
@KtorExperimentalAPI
|
||||
val Module = module {
|
||||
|
||||
single { config }
|
||||
|
||||
single { Requester.RequesterFactory(
|
||||
host = config.host,
|
||||
database = config.database,
|
||||
username = config.username,
|
||||
password = config.password,
|
||||
port = config.port,
|
||||
functionsDirectory = File(this::class.java.getResource("/sql/functions").toURI())
|
||||
).createRequester() }
|
||||
|
||||
single { ArticleRepository(get<Requester>()) }
|
||||
}
|
||||
@@ -52,8 +52,8 @@ fun Application.module() {
|
||||
convert<Article> {
|
||||
decode { values, _ ->
|
||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
?: throw InternalError("Cannot convert $values to Article")
|
||||
get<RepositoryArticle>().findById(id)
|
||||
?: throw InternalError("Cannot convert $values to UUID")
|
||||
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package fr.dcproject
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import java.io.File
|
||||
|
||||
class Config {
|
||||
private var config = ConfigFactory.load()
|
||||
val sqlFiles = File(this::class.java.getResource("/sql").toURI())
|
||||
val envName: String = config.getString("app.envName")
|
||||
|
||||
val host: String = config.getString("db.host")
|
||||
val database: String = config.getString("db.database")
|
||||
val username: String = config.getString("db.username")
|
||||
val password: String = config.getString("db.password")
|
||||
var database: String = config.getString("db.database")
|
||||
var username: String = config.getString("db.username")
|
||||
var password: String = config.getString("db.password")
|
||||
val port: Int = config.getInt("db.port")
|
||||
}
|
||||
26
src/main/kotlin/fr/dcproject/Module.kt
Normal file
26
src/main/kotlin/fr/dcproject/Module.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package fr.dcproject
|
||||
|
||||
import fr.postgresjson.connexion.Connection
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.dsl.module
|
||||
import fr.dcproject.repository.Article as ArticleRepository
|
||||
|
||||
val config = Config()
|
||||
|
||||
@KtorExperimentalAPI
|
||||
val Module = module {
|
||||
|
||||
single { config }
|
||||
|
||||
single { Connection(host = config.host, port = config.port, database = config.database, username = config.username, password = config.password) }
|
||||
|
||||
single { Requester.RequesterFactory(
|
||||
connection = get(),
|
||||
functionsDirectory = config.sqlFiles.resolve("functions")
|
||||
).createRequester() }
|
||||
|
||||
single { ArticleRepository(get()) }
|
||||
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
||||
}
|
||||
@@ -3,22 +3,23 @@ package fr.dcproject.entity
|
||||
import fr.postgresjson.entity.EntityCreatedAt
|
||||
import fr.postgresjson.entity.EntityCreatedAtImp
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
|
||||
|
||||
class Citizen(
|
||||
id: UUID?,
|
||||
id: UUID = UUID.randomUUID(),
|
||||
var name: Name?,
|
||||
var birthday: String?,
|
||||
var userId: String?,
|
||||
var voteAnnonymous: Boolean?,
|
||||
var followAnnonymous: Boolean?,
|
||||
var birthday: DateTime?,
|
||||
var userId: String? = null,
|
||||
var voteAnnonymous: Boolean? = null,
|
||||
var followAnnonymous: Boolean? = null,
|
||||
var user: User?
|
||||
) : UuidEntity(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp() {
|
||||
data class Name(
|
||||
var civility: String?,
|
||||
var firstName: String?,
|
||||
var lastName: String?,
|
||||
var firstName: String?
|
||||
var civility: String? = null
|
||||
)
|
||||
}
|
||||
@@ -5,11 +5,10 @@ import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
|
||||
class User(
|
||||
id: UUID?,
|
||||
id: UUID? = UUID.randomUUID(),
|
||||
var username: String?,
|
||||
var blockedAt: DateTime?,
|
||||
override var createdAt: DateTime?,
|
||||
override var updatedAt: DateTime?
|
||||
var blockedAt: DateTime? = null,
|
||||
var plainPassword: String?
|
||||
) : UuidEntity(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityUpdatedAt by EntityUpdatedAtImp()
|
||||
21
src/main/resources/application.conf
Normal file
21
src/main/resources/application.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
ktor {
|
||||
deployment {
|
||||
port = 8080
|
||||
port = ${?PORT}
|
||||
}
|
||||
application {
|
||||
modules = [ fr.dcproject.ApplicationKt.module ]
|
||||
}
|
||||
}
|
||||
|
||||
app {
|
||||
envName = prod
|
||||
}
|
||||
|
||||
db {
|
||||
host = localhost
|
||||
database = dc-project
|
||||
username = dc-project
|
||||
password = dc-project
|
||||
port = 5432
|
||||
}
|
||||
12
src/main/resources/logback.xml
Normal file
12
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="debug">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
</configuration>
|
||||
17
src/main/resources/sql/fixtures/01-user.sql
Normal file
17
src/main/resources/sql/fixtures/01-user.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
_password text := crypt('azerty', gen_salt('bf', 8));
|
||||
begin
|
||||
delete from "user";
|
||||
insert into "user" (username, password, blocked_at)
|
||||
select
|
||||
'username' || s,
|
||||
_password,
|
||||
case when s % 10 = 0 then now() else null end
|
||||
from generate_series(1, 1000) s;
|
||||
|
||||
raise notice 'user fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
21
src/main/resources/sql/fixtures/02-citizen.sql
Normal file
21
src/main/resources/sql/fixtures/02-citizen.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from citizen;
|
||||
insert into citizen (name, birthday, user_id, vote_annonymous, follow_annonymous)
|
||||
select
|
||||
jsonb_build_object(
|
||||
'first_name', 'first name' || row_number() over (),
|
||||
'last_name', 'LAST NAME' || row_number() over (),
|
||||
'civility', 'm'
|
||||
),
|
||||
now() - interval '25 years',
|
||||
u.id,
|
||||
row_number() over () % 3 = 0,
|
||||
row_number() over () % 5 = 1
|
||||
from "user" u;
|
||||
|
||||
raise notice 'citizen fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
26
src/main/resources/sql/fixtures/03-workgroup.sql
Normal file
26
src/main/resources/sql/fixtures/03-workgroup.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from citizen_in_workgroup;
|
||||
delete from workgroup;
|
||||
|
||||
insert into workgroup (created_by_id, name, description, annonymous, owner_id)
|
||||
select
|
||||
z.id,
|
||||
'name' || rn,
|
||||
'description' || rn,
|
||||
rn % 3 = 1,
|
||||
z.id
|
||||
from (select *, row_number() over () rn from citizen) z;
|
||||
|
||||
insert into citizen_in_workgroup (citizen_id, workgroup_id)
|
||||
select
|
||||
z.id,
|
||||
w.id
|
||||
from (select *, row_number() over ()+5 % 1000 rn from citizen) z
|
||||
join (select *, row_number() over () rn from workgroup) w using (rn);
|
||||
|
||||
raise notice 'workgroup fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
38
src/main/resources/sql/fixtures/04-article.sql
Normal file
38
src/main/resources/sql/fixtures/04-article.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
_tags text[] = $tags$
|
||||
{
|
||||
"nature", "green", "sky",
|
||||
"nuclear", "oil", "black",
|
||||
"love", "human", "scuirel"
|
||||
}
|
||||
$tags$;
|
||||
begin
|
||||
delete from article_relations;
|
||||
delete from article;
|
||||
|
||||
insert into article (version_id, created_by_id, title, annonymous, content, description, tags)
|
||||
select
|
||||
uuid_generate_v4(),
|
||||
z.id,
|
||||
'title' || row_number() over (),
|
||||
row_number() over () % 3 = 0,
|
||||
'content' || row_number() over (),
|
||||
'description' || row_number() over (),
|
||||
_tags[(row_number() over () % 5):(row_number() over () % 9)]
|
||||
from citizen z;
|
||||
|
||||
insert into article_relations (source_id, target_id, created_by_id, comment)
|
||||
select
|
||||
src.id,
|
||||
dest.id,
|
||||
src.created_by_id,
|
||||
'comment' || rn
|
||||
from (select *, row_number() over () rn from article, lateral generate_series(1, 5) g) src
|
||||
join (select *, row_number() over () +5 rn from article) dest using (rn);
|
||||
|
||||
raise notice 'article fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
38
src/main/resources/sql/fixtures/05-constitution.sql
Normal file
38
src/main/resources/sql/fixtures/05-constitution.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from article_in_title;
|
||||
delete from title;
|
||||
delete from constitution;
|
||||
|
||||
insert into constitution (version_id, created_by_id, title, annonymous)
|
||||
select
|
||||
uuid_generate_v4(),
|
||||
z.id,
|
||||
'title' || row_number() over (),
|
||||
row_number() over () % 3 = 0
|
||||
from citizen z;
|
||||
|
||||
insert into title (created_by_id, name, rank, constitution_id)
|
||||
select
|
||||
c.created_by_id,
|
||||
'name' || row_number() over (),
|
||||
row_number() over (),
|
||||
c.id
|
||||
from constitution c,
|
||||
lateral generate_series(1, 5) g;
|
||||
|
||||
insert into article_in_title (created_by_id, rank, title_id, article_id, constitution_id)
|
||||
select
|
||||
ti.created_by_id,
|
||||
row_number() over (),
|
||||
ti.id,
|
||||
a.id,
|
||||
ti.constitution_id
|
||||
from (select *, (row_number() over () % 1005) rn from title, lateral generate_series(1, 3) g) ti
|
||||
join (select *, row_number() over () rn from article) a using (rn);
|
||||
|
||||
raise notice 'constitution fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
30
src/main/resources/sql/fixtures/06-follow.sql
Normal file
30
src/main/resources/sql/fixtures/06-follow.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from follow;
|
||||
|
||||
insert into follow_article (citizen_id, target_id)
|
||||
select
|
||||
z.id,
|
||||
a.id
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5)) z
|
||||
join (select *, row_number() over () rn from article) a using (rn);
|
||||
|
||||
insert into follow_constitution (citizen_id, target_id)
|
||||
select
|
||||
z.id,
|
||||
a.id
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5)) z
|
||||
join (select *, row_number() over () rn from constitution) a using (rn);
|
||||
|
||||
insert into follow_citizen (citizen_id, target_id)
|
||||
select
|
||||
z.id,
|
||||
a.id
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5)) z
|
||||
join (select *, row_number() over () rn from citizen) a using (rn);
|
||||
|
||||
raise notice 'follow fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
43
src/main/resources/sql/fixtures/07-comment.sql
Normal file
43
src/main/resources/sql/fixtures/07-comment.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from comment;
|
||||
|
||||
insert into comment_on_article (citizen_id, target_id, content)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
'content' || (row_number() over () * g)
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5) g) z
|
||||
join (select *, row_number() over () rn from article) a using (rn);
|
||||
|
||||
insert into comment_on_article (citizen_id, target_id, content, parent_id)
|
||||
select
|
||||
z.id,
|
||||
a.target_id,
|
||||
'content' || row_number() over () * g,
|
||||
a.id
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5) g) z
|
||||
join (select *, row_number() over () rn from comment_on_article) a using (rn);
|
||||
|
||||
insert into comment_on_article (citizen_id, target_id, content, parent_id)
|
||||
select
|
||||
z.id,
|
||||
a.target_id,
|
||||
'content' || row_number() over () * g,
|
||||
a.id
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5) g) z
|
||||
join (select *, row_number() over () rn from comment_on_article where parent_id is not null) a using (rn);
|
||||
|
||||
insert into comment_on_constitution (citizen_id, target_id, content)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
'content' || row_number() over () * g
|
||||
from (select *, row_number() over () % 995 rn from citizen, lateral generate_series(1, 5) g) z
|
||||
join (select *, row_number() over () rn from constitution) a using (rn);
|
||||
|
||||
raise notice 'comment fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
48
src/main/resources/sql/fixtures/08-vote.sql
Normal file
48
src/main/resources/sql/fixtures/08-vote.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
do
|
||||
$$
|
||||
begin
|
||||
delete from vote_for_article;
|
||||
delete from vote_for_constitution;
|
||||
delete from vote_for_comment_on_article;
|
||||
delete from vote_for_comment_on_constitution;
|
||||
|
||||
insert into vote_for_article (citizen_id, target_id, note, anonymous)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
(row_number() over () % 3) -1,
|
||||
(row_number() over () % 3 = 1)
|
||||
from (select *, row_number() over () % 995 rn, g from citizen, lateral generate_series(1, 10) g) z
|
||||
join (select *, row_number() over () rn from article) a using (rn);
|
||||
|
||||
insert into vote_for_constitution (citizen_id, target_id, note, anonymous)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
(row_number() over () % 3) -1,
|
||||
(row_number() over () % 3 = 1)
|
||||
from (select *, row_number() over () % 995 rn, g from citizen, lateral generate_series(1, 5) g) z
|
||||
join (select *, row_number() over () rn from constitution) a using (rn);
|
||||
|
||||
insert into vote_for_comment_on_article (citizen_id, target_id, note, anonymous)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
(row_number() over () % 3) -1,
|
||||
(row_number() over () % 3 = 1)
|
||||
from (select *, row_number() over () % 995 rn, g from citizen, lateral generate_series(1, 3) g) z
|
||||
join (select *, row_number() over () rn from comment_on_article) a using (rn);
|
||||
|
||||
insert into vote_for_comment_on_constitution (citizen_id, target_id, note, anonymous)
|
||||
select
|
||||
z.id,
|
||||
a.id,
|
||||
(row_number() over () % 3) -1,
|
||||
(row_number() over () % 3 = 1)
|
||||
from (select *, row_number() over () % 995 rn, g from citizen, lateral generate_series(1, 2) g) z
|
||||
join (select *, row_number() over () rn from comment_on_constitution) a using (rn);
|
||||
|
||||
raise notice 'vote fixtures done';
|
||||
end;
|
||||
$$;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
create or replace function find_article_by_id(in id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
begin
|
||||
select to_json(t)
|
||||
from (
|
||||
select
|
||||
a.*,
|
||||
find_citizen_by_id(a.created_by_id) as created_by
|
||||
into resource
|
||||
from article as a
|
||||
where a.id = _id
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_article_by_id(uuid, out json);
|
||||
41
src/main/resources/sql/functions/article/find_articles.sql
Normal file
41
src/main/resources/sql/functions/article/find_articles.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
create or replace function find_articles(
|
||||
search text default null,
|
||||
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(id) from article)
|
||||
into resource, total
|
||||
from (
|
||||
select
|
||||
a.*,
|
||||
find_citizen_by_id(a.created_by_id) as created_by
|
||||
from article as a
|
||||
where "search" is null or title ilike '%'||"search"||'%'
|
||||
order by
|
||||
case direction when 'asc' then
|
||||
case sort
|
||||
when 'title' then a.title
|
||||
when 'created_at' then a.created_at::text
|
||||
else null
|
||||
end
|
||||
end,
|
||||
case direction when 'desc' then
|
||||
case sort
|
||||
when 'title' then a.title
|
||||
when 'created_at' then a.created_at::text
|
||||
end
|
||||
end
|
||||
desc,
|
||||
a.created_at desc
|
||||
limit "limit" offset "offset"
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_articles(json, int, int);
|
||||
@@ -0,0 +1,20 @@
|
||||
create or replace function find_last_article_by_version_id(in version_id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_version_id alias for version_id;
|
||||
begin
|
||||
select to_json(t)
|
||||
from (
|
||||
select
|
||||
a.*,
|
||||
find_citizen_by_id(a.created_by_id) as created_by
|
||||
into resource
|
||||
from article as a
|
||||
where a.version_id = _version_id
|
||||
order by a.version_number desc
|
||||
limit 1
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_last_article_by_version_id(uuid, inout json);
|
||||
32
src/main/resources/sql/functions/article/upsert_article.sql
Normal file
32
src/main/resources/sql/functions/article/upsert_article.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
create or replace function upsert_article(inout resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
new_id uuid;
|
||||
begin
|
||||
insert into article (version_id, created_by_id, title, annonymous, content, description, tags)
|
||||
select
|
||||
coalesce(version_id, uuid_generate_v4()),
|
||||
(resource#>>'{created_by, id}')::uuid,
|
||||
title,
|
||||
annonymous,
|
||||
content,
|
||||
description,
|
||||
tags
|
||||
from json_populate_record(null::article, resource)
|
||||
returning id into new_id;
|
||||
|
||||
if resource->>'relations' is not null then
|
||||
insert into article_relations (source_id, target_id, created_by_id)
|
||||
select
|
||||
(resource->>'id')::uuid,
|
||||
id,
|
||||
(resource#>>'{created_by, id}')::uuid
|
||||
from json_populate_recordset(null::article, resource->>'relations');
|
||||
end if;
|
||||
|
||||
select find_article_by_id(new_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop procedure if exists upsert_article(inout json);
|
||||
@@ -0,0 +1,16 @@
|
||||
create or replace function find_citizen_by_id(in id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
begin
|
||||
select to_json(t) into resource
|
||||
from (
|
||||
select
|
||||
z.*
|
||||
from citizen as z
|
||||
where z.id = _id
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_citizen_by_id(uuid, inout json);
|
||||
@@ -0,0 +1,17 @@
|
||||
create or replace function find_citizen_by_user_id(in user_id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_user_id alias for user_id;
|
||||
begin
|
||||
select to_json(t) into resource
|
||||
from (
|
||||
select
|
||||
z.*,
|
||||
find_user_by_id(z.user_id) as "user"
|
||||
from citizen as z
|
||||
where z.user_id = _user_id
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_citizen_by_user_id(uuid, inout json);
|
||||
28
src/main/resources/sql/functions/citizen/upsert_citizen.sql
Normal file
28
src/main/resources/sql/functions/citizen/upsert_citizen.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
create or replace function upsert_citizen(inout resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
new_id uuid;
|
||||
begin
|
||||
insert into citizen (id, name, birthday, user_id, vote_annonymous, follow_annonymous)
|
||||
select
|
||||
coalesce(id, uuid_generate_v4()),
|
||||
name,
|
||||
birthday,
|
||||
(resource#>>'{user, id}')::uuid,
|
||||
coalesce(vote_annonymous, true),
|
||||
coalesce(follow_annonymous, true)
|
||||
from json_populate_record(null::citizen, resource)
|
||||
on conflict (id) do update set
|
||||
name = excluded.name,
|
||||
birthday = excluded.birthday,
|
||||
user_id = excluded.user_id,
|
||||
vote_annonymous = excluded.vote_annonymous,
|
||||
follow_annonymous = excluded.follow_annonymous
|
||||
returning id into new_id;
|
||||
|
||||
select find_citizen_by_id(new_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists upsert_citizen(inout json);
|
||||
25
src/main/resources/sql/functions/comment/comment.sql
Normal file
25
src/main/resources/sql/functions/comment/comment.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
create or replace function comment(reference regclass, target_id uuid, citizen_id uuid, content text, parent_id uuid default null, out id uuid)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_citizen_id alias for citizen_id;
|
||||
_target_id alias for target_id;
|
||||
_content alias for content;
|
||||
_parent_id alias for parent_id;
|
||||
_id alias for id;
|
||||
begin
|
||||
if reference = 'article'::regclass then
|
||||
insert into comment_on_article (citizen_id, target_id, content, parent_id)
|
||||
values (_citizen_id, _target_id, _content, _parent_id)
|
||||
returning comment_on_article.id into _id;
|
||||
elseif reference = 'constitution'::regclass then
|
||||
insert into comment_on_constitution (citizen_id, target_id, content, parent_id)
|
||||
values (_citizen_id, _target_id, _content, _parent_id)
|
||||
returning comment_on_constitution.id into _id;
|
||||
else
|
||||
raise exception '% no implemented', reference::text;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists comment(regclass, uuid, uuid, text, uuid);
|
||||
20
src/main/resources/sql/functions/comment/edit_comment.sql
Normal file
20
src/main/resources/sql/functions/comment/edit_comment.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
create or replace function edit_comment(reference regclass, id uuid, content text) returns void
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
_content alias for content;
|
||||
begin
|
||||
if reference = 'article'::regclass then
|
||||
update comment_on_article c set
|
||||
content = _content
|
||||
where c.id = _id;
|
||||
elseif reference = 'constitution'::regclass then
|
||||
update comment_on_constitution c set
|
||||
content = _content
|
||||
where c.id = _id;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists edit_comment(regclass, uuid, uuid, text, uuid);
|
||||
@@ -0,0 +1,35 @@
|
||||
create or replace function create_title_in_constitution(title json, constitution_id uuid default null, out resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_title alias for title;
|
||||
_constitution_id uuid = coalesce(constitution_id, (title#>>'{constitution_id}')::uuid);
|
||||
_author_id uuid = (title#>>'{created_by, id}')::uuid;
|
||||
new_id uuid;
|
||||
begin
|
||||
insert into title (created_by_id, name, rank, constitution_id)
|
||||
select
|
||||
_author_id,
|
||||
ti.name,
|
||||
row_number() OVER (),
|
||||
_constitution_id
|
||||
from json_populate_record(null::title, _title) ti
|
||||
returning id into new_id;
|
||||
|
||||
if (_title->'articles' is not null) then
|
||||
insert into article_in_title (created_by_id, rank, title_id, article_id, constitution_id)
|
||||
select
|
||||
_author_id,
|
||||
row_number() over (),
|
||||
new_id,
|
||||
id,
|
||||
coalesce ((_title->>'constitution_id')::uuid, _constitution_id)
|
||||
from json_populate_recordset(null::article, _title->'articles') ;
|
||||
end if;
|
||||
|
||||
select find_constitution_title_by_id(new_id)
|
||||
into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists create_title_in_constitution(out json);
|
||||
@@ -0,0 +1,19 @@
|
||||
create or replace function find_constitution_by_id(in id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
begin
|
||||
select to_json(t)
|
||||
from (
|
||||
select
|
||||
c.*,
|
||||
find_citizen_by_id(c.created_by_id) as created_by,
|
||||
find_constitution_titles_by_id(c.id) as titles
|
||||
into resource
|
||||
from constitution as c
|
||||
where c.id = _id
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_constitution_by_id(uuid, out json);
|
||||
@@ -0,0 +1,24 @@
|
||||
create or replace function find_constitution_title_by_id(in id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
begin
|
||||
select to_json(t)
|
||||
from (
|
||||
select
|
||||
ti.id,
|
||||
ti.name,
|
||||
ti.rank,
|
||||
array_agg(a order by ait.rank) as articles
|
||||
into resource
|
||||
from title as ti
|
||||
left join article_in_title ait on ti.id = ait.title_id
|
||||
left join article a on ait.article_id = a.id
|
||||
where ti.id = _id
|
||||
group by ti.id
|
||||
order by ti.rank
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_constitution_title_by_id(uuid, out json);
|
||||
@@ -0,0 +1,20 @@
|
||||
create or replace function find_constitution_titles_by_id(in constitution_id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_constitution_id alias for constitution_id;
|
||||
begin
|
||||
select json_agg(t)
|
||||
from (
|
||||
select
|
||||
ti.id,
|
||||
ti.name,
|
||||
ti.rank
|
||||
into resource
|
||||
from title as ti
|
||||
where ti.constitution_id = _constitution_id
|
||||
order by ti.rank
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_constitution_titles_by_id(uuid, out json);
|
||||
@@ -0,0 +1,33 @@
|
||||
create or replace procedure upsert_constitution(inout resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
titles json;
|
||||
_title json;
|
||||
_citizen_id uuid = (resource#>>'{created_by, id}')::uuid;
|
||||
new_id uuid;
|
||||
begin
|
||||
insert into constitution (version_id, created_by_id, title, annonymous)
|
||||
select
|
||||
version_id,
|
||||
_citizen_id,
|
||||
title,
|
||||
annonymous
|
||||
from json_populate_record(null::constitution, resource)
|
||||
returning id into new_id;
|
||||
|
||||
titles := (resource->>'titles');
|
||||
|
||||
for _title in select json_array_elements(titles) loop
|
||||
if _title#>>'{created_by, id}' is null then
|
||||
_title := jsonb_set(_title::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
end if;
|
||||
|
||||
perform create_title_in_constitution(_title, new_id);
|
||||
end loop;
|
||||
|
||||
select find_constitution_by_id(new_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop procedure if exists upsert_constitution(inout json);
|
||||
23
src/main/resources/sql/functions/follow/follow.sql
Normal file
23
src/main/resources/sql/functions/follow/follow.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
create or replace function follow(reference regclass, _target_id uuid, _citizen_id uuid) returns void
|
||||
language plpgsql as
|
||||
$$
|
||||
begin
|
||||
if reference = 'article'::regclass then
|
||||
insert into follow_article (citizen_id, target_id)
|
||||
values (_citizen_id, _target_id)
|
||||
on conflict (citizen_id, target_id) do nothing;
|
||||
elseif reference = 'constitution'::regclass then
|
||||
insert into follow_constitution (citizen_id, target_id)
|
||||
values (_citizen_id, _target_id)
|
||||
on conflict (citizen_id, target_id) do nothing;
|
||||
elseif reference = 'citizen'::regclass then
|
||||
insert into follow_citizen (citizen_id, target_id)
|
||||
values (_citizen_id, _target_id)
|
||||
on conflict (citizen_id, target_id) do nothing;
|
||||
else
|
||||
raise exception '% no implemented', reference::text;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists follow(regclass, uuid, uuid);
|
||||
16
src/main/resources/sql/functions/follow/unfollow.sql
Normal file
16
src/main/resources/sql/functions/follow/unfollow.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
create or replace function unfollow(reference regclass, target_id uuid, citizen_id uuid) returns void
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_citizen_id alias for citizen_id;
|
||||
_target_id alias for target_id;
|
||||
begin
|
||||
delete
|
||||
from follow f
|
||||
where f.citizen_id = _citizen_id
|
||||
and f.target_id = _target_id
|
||||
and f.target_reference = reference;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists unfollow(regclass, uuid, uuid);
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE OR REPLACE FUNCTION random_between(low INT ,high INT)
|
||||
RETURNS INT AS
|
||||
$$
|
||||
BEGIN
|
||||
RETURN floor(random()* (high-low + 1) + low);
|
||||
END;
|
||||
$$ language 'plpgsql' STRICT;
|
||||
18
src/main/resources/sql/functions/user/check_user.sql
Normal file
18
src/main/resources/sql/functions/user/check_user.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
create or replace function check_user(in username text, in plain_password text, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_username alias for username;
|
||||
begin
|
||||
select
|
||||
case when count(u) = 1
|
||||
then to_jsonb(u) - 'password'
|
||||
else null end
|
||||
into resource
|
||||
from "user" as u
|
||||
where u.username = lower(_username)
|
||||
and u.password = crypt(plain_password, u.password)
|
||||
group by u;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists check_user(text, text, out json);
|
||||
12
src/main/resources/sql/functions/user/find_user_by_id.sql
Normal file
12
src/main/resources/sql/functions/user/find_user_by_id.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
create or replace function find_user_by_id(in id uuid, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_id alias for id;
|
||||
begin
|
||||
select to_jsonb(u) - 'password' into resource
|
||||
from "user" as u
|
||||
where u.id = _id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_user_by_id(uuid, out json);
|
||||
@@ -0,0 +1,12 @@
|
||||
create or replace function find_user_by_username(in username text, out resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_username alias for username;
|
||||
begin
|
||||
select to_jsonb(u) - 'password' into resource
|
||||
from "user" as u
|
||||
where u.username = _username;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists find_user_by_username(text, out json);
|
||||
19
src/main/resources/sql/functions/user/insert_user.sql
Normal file
19
src/main/resources/sql/functions/user/insert_user.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
create or replace function insert_user(inout resource json) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
new_id uuid;
|
||||
begin
|
||||
insert into "user" (id, username, password, blocked_at)
|
||||
select
|
||||
coalesce(t.id, uuid_generate_v4()),
|
||||
t.username,
|
||||
crypt(resource->>'plain_password', gen_salt('bf', 8)),
|
||||
case when t.blocked_at is not null then now() else null end
|
||||
from json_populate_record(null::"user", resource) t
|
||||
returning id into new_id;
|
||||
|
||||
select find_user_by_id(new_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists insert_user(inout json);
|
||||
35
src/main/resources/sql/functions/vote/vote.sql
Normal file
35
src/main/resources/sql/functions/vote/vote.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
create or replace function vote(reference regclass, _target_id uuid, _citizen_id uuid, _note int, _anonymous bool default true) returns void
|
||||
language plpgsql as
|
||||
$$
|
||||
begin
|
||||
if reference = 'article'::regclass then
|
||||
insert into vote_for_article (citizen_id, target_id, note, anonymous)
|
||||
values (_citizen_id, _target_id, _note, _anonymous)
|
||||
on conflict (citizen_id, target_id) do update set
|
||||
note = excluded.note,
|
||||
anonymous = excluded.anonymous;
|
||||
elseif reference = 'constitution'::regclass then
|
||||
insert into vote_for_constitution (citizen_id, target_id, note, anonymous)
|
||||
values (_citizen_id, _target_id, _note, _anonymous)
|
||||
on conflict (citizen_id, target_id) do update set
|
||||
note = excluded.note,
|
||||
anonymous = excluded.anonymous;
|
||||
elseif reference = 'comment_on_article'::regclass then
|
||||
insert into vote_for_comment_on_article (citizen_id, target_id, note, anonymous)
|
||||
values (_citizen_id, _target_id, _note, _anonymous)
|
||||
on conflict (citizen_id, target_id) do update set
|
||||
note = excluded.note,
|
||||
anonymous = excluded.anonymous;
|
||||
elseif reference = 'comment_on_constitution'::regclass then
|
||||
insert into vote_for_comment_on_constitution (citizen_id, target_id, note, anonymous)
|
||||
values (_citizen_id, _target_id, _note, _anonymous)
|
||||
on conflict (citizen_id, target_id) do update set
|
||||
note = excluded.note,
|
||||
anonymous = excluded.anonymous;
|
||||
else
|
||||
raise exception '% no implemented', reference::text;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- drop function if exists vote(regclass,uuid,uuid,integer,boolean);
|
||||
42
src/main/resources/sql/migrations/0000-init_schema.down.sql
Normal file
42
src/main/resources/sql/migrations/0000-init_schema.down.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- Stats
|
||||
drop table if exists resource_view;
|
||||
|
||||
-- Extra resources
|
||||
drop table if exists follow_article;
|
||||
drop table if exists follow_constitution;
|
||||
drop table if exists follow_citizen;
|
||||
drop table if exists follow;
|
||||
|
||||
drop table if exists vote_for_article;
|
||||
drop table if exists vote_for_constitution;
|
||||
drop table if exists vote_for_comment_on_article;
|
||||
drop table if exists vote_for_comment_on_constitution;
|
||||
drop table if exists vote;
|
||||
|
||||
drop table if exists comment_on_article;
|
||||
drop table if exists comment_on_constitution;
|
||||
drop table if exists comment;
|
||||
|
||||
drop table if exists extra;
|
||||
|
||||
-- Article & Contitution
|
||||
drop table if exists article_relations;
|
||||
drop trigger if exists set_constitution_link_trigger on article_on_title;
|
||||
drop table if exists article_in_title;
|
||||
drop table if exists title;
|
||||
drop function if exists set_constitution_link();
|
||||
|
||||
drop trigger if exists generate_version_number_trigger on article;
|
||||
drop table if exists article;
|
||||
drop function if exists generate_version_number(regclass, uuid);
|
||||
drop trigger if exists generate_version_number_trigger on constitution;
|
||||
drop table if exists constitution;
|
||||
drop function if exists set_version_number();
|
||||
|
||||
-- User
|
||||
drop table if exists moderator;
|
||||
drop table if exists citizen_in_workgroup;
|
||||
drop table if exists workgroup;
|
||||
drop table if exists citizen;
|
||||
drop table if exists "user";
|
||||
drop type if exists public."name";
|
||||
310
src/main/resources/sql/migrations/0000-init_schema.up.sql
Normal file
310
src/main/resources/sql/migrations/0000-init_schema.up.sql
Normal file
@@ -0,0 +1,310 @@
|
||||
-- Users
|
||||
create table "user"
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
updated_at timestamptz default now() not null check ( updated_at >= created_at ),
|
||||
blocked_at timestamptz default null null,
|
||||
username varchar(64) not null check ( username != '' and lower(username) = username) unique,
|
||||
password text not null check ( password != '' )
|
||||
);
|
||||
|
||||
create table citizen
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
name jsonb not null check ( name ? 'first_name' and name ? 'last_name' ),
|
||||
birthday date not null,
|
||||
user_id uuid not null references "user" (id) unique,
|
||||
vote_annonymous boolean default true not null,
|
||||
follow_annonymous boolean default true not null
|
||||
);
|
||||
|
||||
create table workgroup
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
updated_at timestamptz default now() not null check ( updated_at >= created_at ),
|
||||
created_by_id uuid not null references citizen (id),
|
||||
name varchar(128) not null,
|
||||
description text null,
|
||||
annonymous boolean default false not null,
|
||||
logo text null,
|
||||
owner_id uuid not null references citizen (id)
|
||||
);
|
||||
|
||||
create table citizen_in_workgroup
|
||||
(
|
||||
citizen_id uuid not null references citizen (id),
|
||||
workgroup_id uuid not null references workgroup (id),
|
||||
created_at timestamptz default now() not null,
|
||||
primary key (citizen_id, workgroup_id)
|
||||
);
|
||||
|
||||
create table moderator
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
updated_at timestamptz default now() not null check ( updated_at >= created_at ),
|
||||
assigned_period tstzrange[] default '{}' not null,
|
||||
user_id uuid not null references "user" (id)
|
||||
);
|
||||
|
||||
-- Article & Constitution
|
||||
|
||||
create or replace function generate_version_number(tablename regclass, version_id uuid, out generated_number int)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_version_id alias for version_id;
|
||||
begin
|
||||
if (tablename = 'article'::regclass) then
|
||||
select version_number + 1
|
||||
into generated_number
|
||||
from article as t
|
||||
where t.version_id = _version_id
|
||||
order by version_number desc
|
||||
limit 1;
|
||||
elseif tablename = 'constitution'::regclass then
|
||||
select version_number + 1
|
||||
into generated_number
|
||||
from constitution as t
|
||||
where t.version_id = _version_id
|
||||
order by version_number desc
|
||||
limit 1;
|
||||
else
|
||||
raise exception '% is not implemented', tablename::text;
|
||||
end if;
|
||||
|
||||
if not found then
|
||||
generated_number := 1;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function set_version_number() returns trigger
|
||||
language plpgsql as
|
||||
$$
|
||||
begin
|
||||
new.version_number = generate_version_number(tg_table_name::regclass, new.version_id);
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create table article
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
created_by_id uuid not null references citizen (id),
|
||||
version_id uuid default uuid_generate_v4() not null,
|
||||
version_number int not null,
|
||||
title text not null,
|
||||
annonymous boolean default false not null,
|
||||
content text not null check ( content != '' ),
|
||||
description text,
|
||||
tags varchar(32)[] default '{}' not null,
|
||||
unique (version_id, version_number)
|
||||
);
|
||||
|
||||
create trigger generate_version_number_trigger
|
||||
before insert
|
||||
on article
|
||||
for each row
|
||||
execute function set_version_number();
|
||||
|
||||
create table constitution
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
created_by_id uuid not null references citizen (id),
|
||||
version_id uuid default uuid_generate_v4() not null,
|
||||
version_number int not null,
|
||||
title text not null,
|
||||
annonymous boolean default false not null
|
||||
);
|
||||
|
||||
create trigger generate_version_number_trigger
|
||||
before insert
|
||||
on constitution
|
||||
for each row
|
||||
execute procedure set_version_number();
|
||||
|
||||
create table title
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
created_by_id uuid not null references citizen (id),
|
||||
name text not null check ( name != '' ),
|
||||
rank int not null,
|
||||
constitution_id uuid not null references constitution (id)
|
||||
);
|
||||
|
||||
create table article_in_title
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
created_by_id uuid not null references citizen (id),
|
||||
rank int not null,
|
||||
title_id uuid not null references title (id),
|
||||
article_id uuid not null references article (id),
|
||||
constitution_id uuid not null references constitution (id)
|
||||
);
|
||||
|
||||
create or replace function set_constitution_link() returns trigger
|
||||
language plpgsql as
|
||||
$$
|
||||
begin
|
||||
new.constitution_id = (
|
||||
select t.constitution_id
|
||||
from title as t
|
||||
where t.id = new.title_id
|
||||
);
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger set_constitution_link_trigger
|
||||
before insert
|
||||
on article_in_title
|
||||
execute procedure set_constitution_link();
|
||||
|
||||
create table article_relations
|
||||
(
|
||||
source_id uuid references article,
|
||||
target_id uuid references article check ( source_id != target_id ),
|
||||
created_at timestamptz default now(),
|
||||
created_by_id uuid not null references citizen (id),
|
||||
comment text null,
|
||||
primary key (source_id, target_id)
|
||||
);
|
||||
|
||||
-- Extra resources
|
||||
|
||||
create table extra
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
created_at timestamptz default now() not null,
|
||||
citizen_id uuid not null references citizen (id),
|
||||
target_id uuid not null,
|
||||
target_reference regclass not null
|
||||
);
|
||||
|
||||
create table follow
|
||||
(
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (extra);
|
||||
|
||||
create table follow_article
|
||||
(
|
||||
target_reference regclass default 'article'::regclass not null,
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
foreign key (target_id) references article (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (follow);
|
||||
|
||||
create table follow_constitution
|
||||
(
|
||||
target_reference regclass default 'constitution'::regclass not null,
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
foreign key (target_id) references constitution (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (follow);
|
||||
|
||||
create table follow_citizen
|
||||
(
|
||||
target_reference regclass default 'citizen'::regclass not null,
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
foreign key (target_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (follow);
|
||||
|
||||
|
||||
|
||||
create table comment
|
||||
(
|
||||
updated_at timestamptz default now() not null check ( updated_at >= created_at ),
|
||||
"content" text not null check ( content != '' ),
|
||||
parent_id uuid null references comment (id),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id)
|
||||
) inherits (extra);
|
||||
|
||||
create table comment_on_article
|
||||
(
|
||||
target_reference regclass default 'article'::regclass not null,
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
foreign key (target_id) references article (id),
|
||||
foreign key (parent_id) references comment_on_article (id),
|
||||
primary key (id)
|
||||
) inherits (comment);
|
||||
|
||||
create table comment_on_constitution
|
||||
(
|
||||
target_reference regclass default 'constitution'::regclass not null,
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
foreign key (target_id) references constitution (id),
|
||||
foreign key (parent_id) references comment_on_constitution (id),
|
||||
primary key (id)
|
||||
) inherits (comment);
|
||||
|
||||
|
||||
|
||||
create table vote
|
||||
(
|
||||
anonymous boolean default true not null,
|
||||
note int not null check ( note >= -1 and note <= 1 ),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (extra);
|
||||
|
||||
create table vote_for_article
|
||||
(
|
||||
target_reference regclass default 'article'::regclass not null,
|
||||
foreign key (target_id) references article (id),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (vote);
|
||||
|
||||
create table vote_for_constitution
|
||||
(
|
||||
target_reference regclass default 'constitution'::regclass not null,
|
||||
foreign key (target_id) references constitution (id),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (vote);
|
||||
|
||||
create table vote_for_comment_on_article
|
||||
(
|
||||
target_reference regclass default 'comment_on_article'::regclass not null,
|
||||
foreign key (target_id) references comment_on_article (id),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (vote);
|
||||
|
||||
create table vote_for_comment_on_constitution
|
||||
(
|
||||
target_reference regclass default 'comment_on_constitution'::regclass not null,
|
||||
foreign key (target_id) references comment_on_constitution (id),
|
||||
foreign key (citizen_id) references citizen (id),
|
||||
primary key (id),
|
||||
unique (citizen_id, target_id)
|
||||
) inherits (vote);
|
||||
|
||||
-- Stats
|
||||
create table resource_view
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null primary key,
|
||||
type regclass not null,
|
||||
created_at timestamptz default now() not null,
|
||||
created_by_id uuid null references citizen (id),
|
||||
ip cidr null
|
||||
);
|
||||
53
src/main/resources/sql/tests/article.sql
Normal file
53
src/main/resources/sql/tests/article.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
_user_id uuid;
|
||||
_citizen_id uuid;
|
||||
created_citizen json := '{"name": {"first_name":"George", "last_name":"MICHEL"}, "birthday": "2001-01-01"}';
|
||||
created_article json := '{"version_id":"933b6a1b-50c9-42b6-989f-c02a57814ef9", "title": "Love the world", "annonymous": false, "content": "bla bal bla", "tags": ["love", "test"]}';
|
||||
selected_article json;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
_user_id := created_user->>'id';
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
|
||||
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);
|
||||
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen);
|
||||
_citizen_id := created_citizen->>'id';
|
||||
created_article := jsonb_set(created_article::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
assert created_article#>>'{created_by, id}' = _citizen_id::text, format('citizenId in article must be the same as citizen, %s != %s', created_article#>>'{created_by, id}', _citizen_id::text);
|
||||
|
||||
-- upsert article
|
||||
call upsert_article(created_article);
|
||||
assert created_article->>'version_id' is not null, 'version_id should not be null';
|
||||
assert (created_article->>'version_number')::int = 1, format('version_number must be equal to 1, %s instead', created_article->>'version_number');
|
||||
-- try tu create new version
|
||||
call upsert_article(created_article);
|
||||
assert (created_article->>'version_number')::int = 2, format('version_number must be equal to 2, %s instead', created_article->>'version_number');
|
||||
|
||||
-- get article by id and check the title
|
||||
select find_article_by_id((created_article->>'id')::uuid) into selected_article;
|
||||
assert selected_article->>'title' = 'Love the world', format('title must be "Love the world", %s', selected_article->>'title');
|
||||
|
||||
-- get article by version_id and check the title
|
||||
select find_last_article_by_version_id((created_article->>'version_id')::uuid) into selected_article;
|
||||
assert selected_article->>'title' = 'Love the world', format('title must be "Love the world", %s', selected_article->>'title');
|
||||
assert (selected_article->>'version_number')::int = 2, format('version_id must be 2, %s instead', selected_article->>'version_number');
|
||||
-- check if user id is returned
|
||||
assert (selected_article#>>'{created_by, user, id}')::uuid = _user_id, format('user_id must be %s instead of %s', _user_id, (selected_article#>>'{created_by, user, id}')::uuid);
|
||||
|
||||
-- delete article and context
|
||||
delete from article;
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
-- check if find by id return null if article not exist
|
||||
select find_citizen_by_user_id((created_citizen->>'id')::uuid) into selected_article;
|
||||
assert selected_article is null, format('article must be null if not exist, %s', selected_article);
|
||||
|
||||
raise notice 'article test pass';
|
||||
end;
|
||||
$$;
|
||||
46
src/main/resources/sql/tests/citizen.sql
Normal file
46
src/main/resources/sql/tests/citizen.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
wrong_citizen json;
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
_user_id uuid;
|
||||
created_citizen json := '{"name": {"first_name":"George", "last_name":"MICHEL"}, "birthday": "2001-01-01"}';
|
||||
selected_citizen json;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
_user_id := created_user->>'id';
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
|
||||
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);
|
||||
|
||||
-- insert new citizen
|
||||
call upsert_citizen(created_citizen);
|
||||
assert created_citizen->>'birthday' = '2001-01-01'::text, format('birthday of inserted citizen must be the same of the original object, %s != %s', created_citizen->>'birthday', '2001-01-01'::text);
|
||||
|
||||
-- insert citizen without first name and test if throw exception
|
||||
wrong_citizen := (created_citizen::jsonb - '{name, first_name}'::text[])::json;
|
||||
begin
|
||||
call upsert_citizen(wrong_citizen);
|
||||
assert false, 'upsert_citizen must be throw exception if first_name not exist';
|
||||
exception when not_null_violation then
|
||||
end;
|
||||
|
||||
-- get citizen by id and check the first name
|
||||
select find_citizen_by_id((created_citizen->>'id')::uuid) into selected_citizen;
|
||||
assert selected_citizen#>>'{name, first_name}' = 'George', format('first name must be George, %s', selected_citizen#>>'{name, first_name}');
|
||||
|
||||
-- get citizen by user id and check the first name
|
||||
select find_citizen_by_user_id((created_citizen->>'user_id')::uuid) into selected_citizen;
|
||||
assert selected_citizen#>>'{name, first_name}' = 'George', format('first name must be George, %s', selected_citizen#>>'{name, first_name}');
|
||||
|
||||
-- delete citizen
|
||||
delete from citizen where user_id = _user_id;
|
||||
delete from "user" where username = 'george';
|
||||
|
||||
-- check if fint by id return null if citizen not exist
|
||||
select find_citizen_by_user_id((created_citizen->>'user_id')::uuid) into selected_citizen;
|
||||
assert selected_citizen is null, format('citizen must be null if not exist, %s', selected_citizen);
|
||||
|
||||
raise notice 'citizen test pass';
|
||||
end;
|
||||
$$;
|
||||
71
src/main/resources/sql/tests/comment.sql
Normal file
71
src/main/resources/sql/tests/comment.sql
Normal file
@@ -0,0 +1,71 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
created_user2 json := '{"username": "john", "plain_password": "qwerty"}';
|
||||
_citizen_id uuid;
|
||||
created_citizen json := $json$
|
||||
{
|
||||
"name": {
|
||||
"first_name": "George",
|
||||
"last_name": "MICHEL"
|
||||
},
|
||||
"birthday": "2001-01-01"
|
||||
}
|
||||
$json$;
|
||||
created_article json := $json$
|
||||
{
|
||||
"version_id": "933b6a1b-50c9-42b6-989f-c02a57814ef9",
|
||||
"title": "Love the world",
|
||||
"annonymous": false,
|
||||
"content": "bla bal bla",
|
||||
"tags": [
|
||||
"love",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
$json$;
|
||||
_comment_id uuid;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
select insert_user(created_user2) into created_user2;
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
|
||||
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen);
|
||||
_citizen_id := created_citizen->>'id';
|
||||
created_article := jsonb_set(created_article::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
assert created_article#>>'{created_by, id}' = _citizen_id::text, format('citizenId in article must be the same as citizen, %s != %s', created_article#>>'{created_by, id}', _citizen_id::text);
|
||||
-- upsert article
|
||||
call upsert_article(created_article);
|
||||
|
||||
|
||||
select comment(
|
||||
reference => 'article'::regclass,
|
||||
target_id => (created_article->>'id')::uuid,
|
||||
citizen_id => _citizen_id,
|
||||
content => 'Ho my god !'::text
|
||||
) into _comment_id;
|
||||
assert (select count(*) = 1 from "comment"), 'comment must be inserted';
|
||||
|
||||
perform edit_comment(
|
||||
reference => 'article'::regclass,
|
||||
id => _comment_id,
|
||||
content => 'edited'::text
|
||||
);
|
||||
assert (select count(*) = 1 from "comment"), 'edit comment must not insert new comment';
|
||||
assert (select count(*) = 1 from "comment" where content = 'edited'), 'edit comment must not insert new comment';
|
||||
|
||||
-- delete comment and context
|
||||
delete from "comment";
|
||||
delete from article;
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
raise notice 'comment test pass';
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
-- select uuid_generate_v4();
|
||||
81
src/main/resources/sql/tests/constitution.sql
Normal file
81
src/main/resources/sql/tests/constitution.sql
Normal file
@@ -0,0 +1,81 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
_user_id uuid;
|
||||
_citizen_id uuid;
|
||||
created_citizen json := $json$
|
||||
{
|
||||
"name": {
|
||||
"first_name": "George",
|
||||
"last_name": "MICHEL"
|
||||
},
|
||||
"birthday": "2001-01-01"
|
||||
}
|
||||
$json$;
|
||||
created_article json := $json$
|
||||
{
|
||||
"version_id": "933b6a1b-50c9-42b6-989f-c02a57814ef9",
|
||||
"title": "Love the world",
|
||||
"annonymous": false,
|
||||
"content": "bla bal bla",
|
||||
"tags": [
|
||||
"love",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
$json$;
|
||||
created_constitution json := $json$
|
||||
{
|
||||
"version_id": "18ff6dd6-3bc1-4c59-82f0-5e2a8d54ae3e",
|
||||
"title": "Love the world",
|
||||
"annonymous": false,
|
||||
"titles": [
|
||||
{
|
||||
"name": "titleOne"
|
||||
},
|
||||
{
|
||||
"name": "titleTwo"
|
||||
}
|
||||
]
|
||||
}
|
||||
$json$;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
_user_id := created_user->>'id';
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
|
||||
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);
|
||||
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen);
|
||||
_citizen_id := created_citizen->>'id';
|
||||
created_article := jsonb_set(created_article::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
assert created_article#>>'{created_by, id}' = _citizen_id::text, format('citizenId in article must be the same as citizen, %s != %s', created_article#>>'{created_by, id}', _citizen_id::text);
|
||||
|
||||
-- upsert article for context
|
||||
call upsert_article(created_article);
|
||||
assert created_article->>'version_id' is not null, 'version_id should not be null';
|
||||
|
||||
|
||||
-- create new constitution
|
||||
created_constitution := jsonb_set(created_constitution::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
created_constitution := jsonb_set(created_constitution::jsonb, '{titles, 0, articles}'::text[], jsonb_build_array(jsonb_build_object('id', created_article->>'id')), true)::json;
|
||||
call upsert_constitution(created_constitution);
|
||||
assert (created_constitution->>'version_number')::int = 1, format('version_number must be equal to 1, %s instead', created_constitution->>'version_number');
|
||||
assert created_constitution#>>'{titles, 0, name}' = 'titleOne'::text, format('the name of the first title of contitution must be %s, not %s', 'titleOne', created_constitution#>>'{titles, 0, name}');
|
||||
|
||||
-- delete article and context
|
||||
delete from article_in_title;
|
||||
delete from title;
|
||||
delete from constitution;
|
||||
delete from article;
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
raise notice 'constitution test pass';
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
-- select uuid_generate_v4();
|
||||
59
src/main/resources/sql/tests/follow.sql
Normal file
59
src/main/resources/sql/tests/follow.sql
Normal file
@@ -0,0 +1,59 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
created_user2 json := '{"username": "john", "plain_password": "qwerty"}';
|
||||
_citizen_id uuid;
|
||||
_citizen_id2 uuid;
|
||||
created_citizen json := $json$
|
||||
{
|
||||
"name": {
|
||||
"first_name": "George",
|
||||
"last_name": "MICHEL"
|
||||
},
|
||||
"birthday": "2001-01-01"
|
||||
}
|
||||
$json$;
|
||||
created_citizen2 json := $json$
|
||||
{
|
||||
"name": {
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
},
|
||||
"birthday": "2002-01-01"
|
||||
}
|
||||
$json$;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
select insert_user(created_user2) into created_user2;
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
|
||||
created_citizen2 := jsonb_set(created_citizen2::jsonb, '{user}'::text[], jsonb_build_object('id', created_user2->>'id'), true)::json;
|
||||
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen);
|
||||
_citizen_id := created_citizen->>'id';
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen2);
|
||||
_citizen_id2 := created_citizen2->>'id';
|
||||
|
||||
|
||||
perform follow('citizen'::regclass, _citizen_id, _citizen_id2);
|
||||
assert (select count(*) > 0 from follow), 'follow must be inserted';
|
||||
|
||||
perform follow('citizen'::regclass, _citizen_id, _citizen_id2);
|
||||
assert (select count(*) > 0 from follow), 'follow must be inserted';
|
||||
|
||||
perform unfollow('citizen'::regclass, _citizen_id, _citizen_id2);
|
||||
assert (select count(*) = 0 from follow), 'follow must be deleted after unfollow';
|
||||
|
||||
-- delete follow and context
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
raise notice 'follow test pass';
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
-- select uuid_generate_v4();
|
||||
35
src/main/resources/sql/tests/user.sql
Normal file
35
src/main/resources/sql/tests/user.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
selected_user json;
|
||||
exist_user json;
|
||||
begin
|
||||
-- Insert user and check if username and password is correct
|
||||
select insert_user(created_user) into created_user;
|
||||
assert created_user->>'username' = 'george', 'username must be george';
|
||||
assert created_user->>'password' is null, 'password must not be returned';
|
||||
|
||||
-- get user by there id and check the username is correct
|
||||
select find_user_by_id((created_user->>'id')::uuid) into selected_user;
|
||||
assert selected_user->>'username' = 'george', format('username must be george, %s instead', selected_user);
|
||||
|
||||
-- get user by username and check the username is correct
|
||||
select find_user_by_username(created_user->>'username') into selected_user;
|
||||
assert selected_user->>'username' = 'george', 'username must be george';
|
||||
|
||||
-- check if user exist with username and password and verify the reterned user
|
||||
select check_user('george', 'azerty') into exist_user;
|
||||
assert exist_user is not null, format('the function check_user must be return user object if username and password is correct, %s is return', exist_user::text);
|
||||
assert exist_user->>'username' = 'george', format('the function check_user must be return user object with username is "george", %s is return', exist_user::text);
|
||||
assert exist_user->>'password' is null, format('the function check_user must not be return the password, %s is return', exist_user::text);
|
||||
|
||||
-- delete user and check if user is really not exists
|
||||
delete from "user" where username = 'george';
|
||||
select check_user('george', 'azerty') into exist_user;
|
||||
assert exist_user is null, format('the function check_user must be return null if user not exist, %s is return', exist_user::text);
|
||||
|
||||
raise notice 'user test pass';
|
||||
end;
|
||||
$$;
|
||||
|
||||
82
src/main/resources/sql/tests/vote.sql
Normal file
82
src/main/resources/sql/tests/vote.sql
Normal file
@@ -0,0 +1,82 @@
|
||||
do
|
||||
$$
|
||||
declare
|
||||
created_user json := '{"username": "george", "plain_password": "azerty"}';
|
||||
_citizen_id uuid;
|
||||
created_citizen json := $json$
|
||||
{
|
||||
"name": {
|
||||
"first_name": "George",
|
||||
"last_name": "MICHEL"
|
||||
},
|
||||
"birthday": "2001-01-01"
|
||||
}
|
||||
$json$;
|
||||
created_article json := $json$
|
||||
{
|
||||
"version_id": "933b6a1b-50c9-42b6-989f-c02a57814ef9",
|
||||
"title": "Love the world",
|
||||
"annonymous": false,
|
||||
"content": "bla bal bla",
|
||||
"tags": [
|
||||
"love",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
$json$;
|
||||
begin
|
||||
-- insert user for context
|
||||
select insert_user(created_user) into created_user;
|
||||
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
|
||||
|
||||
-- insert new citizen for context
|
||||
call upsert_citizen(created_citizen);
|
||||
_citizen_id := created_citizen->>'id';
|
||||
created_article := jsonb_set(created_article::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
||||
assert created_article#>>'{created_by, id}' = _citizen_id::text, format('citizenId in article must be the same as citizen, %s != %s', created_article#>>'{created_by, id}', _citizen_id::text);
|
||||
-- upsert article
|
||||
call upsert_article(created_article);
|
||||
|
||||
|
||||
perform vote(
|
||||
reference => 'article'::regclass,
|
||||
_target_id => (created_article->>'id')::uuid,
|
||||
_citizen_id => _citizen_id,
|
||||
_note => 1
|
||||
);
|
||||
assert (select count(*) = 1 from vote_for_article), 'vote must be inserted';
|
||||
assert (select note = 1 from vote_for_article limit 1), 'vote must be equal to 1';
|
||||
|
||||
perform vote(
|
||||
reference => 'article'::regclass,
|
||||
_target_id => (created_article->>'id')::uuid,
|
||||
_citizen_id => _citizen_id,
|
||||
_note => -1
|
||||
);
|
||||
assert (select count(*) = 1 from vote_for_article), 'vote must be inserted';
|
||||
assert (select note = -1 from vote_for_article limit 1), 'vote must be equal to -1';
|
||||
|
||||
begin
|
||||
perform vote(
|
||||
reference => 'article'::regclass,
|
||||
_target_id => (created_article->>'id')::uuid,
|
||||
_citizen_id => _citizen_id,
|
||||
_note => -10
|
||||
);
|
||||
assert false, 'vote must be throw exception if note is not -1, 0 or 1';
|
||||
exception when check_violation then
|
||||
end;
|
||||
|
||||
|
||||
-- delete vote and context
|
||||
delete from vote;
|
||||
delete from article;
|
||||
delete from citizen;
|
||||
delete from "user";
|
||||
|
||||
raise notice 'vote test pass';
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
-- select uuid_generate_v4();
|
||||
96
src/test/kotlin/ArticleRouteTest.kt
Normal file
96
src/test/kotlin/ArticleRouteTest.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package fr.dcproject
|
||||
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.server.testing.handleRequest
|
||||
import io.ktor.server.testing.setBody
|
||||
import io.ktor.server.testing.withTestApplication
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
@TestInstance(PER_CLASS)
|
||||
class ArticleRouteTest: KoinTest {
|
||||
private val migrations: Migrations by inject()
|
||||
|
||||
@BeforeEach
|
||||
fun beforeAll() {
|
||||
startKoin {
|
||||
modules(Module)
|
||||
config.database = "test"
|
||||
config.username = "test"
|
||||
config.password = "test"
|
||||
|
||||
}
|
||||
migrations.run()
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun afterAll() {
|
||||
migrations.forceAllDown()
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
private val article: String = """{
|
||||
"id" : "8e8dd0aa-2b2b-41e1-bff5-ea613c988774",
|
||||
"version_id" : "e3ec9ea8-87ac-46ac-8321-8f2bc8c687bc",
|
||||
"version_number" : 1,
|
||||
"title" : "title13",
|
||||
"annonymous" : false,
|
||||
"content" : "content13",
|
||||
"description" : "description13",
|
||||
"tags" : [ "sky", "nuclear" ],
|
||||
"created_at" : "2019-07-30T14:08:51.420Z",
|
||||
"created_by" : {
|
||||
"id" : "d821a211-10d6-4d65-b0db-e0bd33d21761",
|
||||
"name" : {
|
||||
"civility" : "m",
|
||||
"last_name" : "LAST NAME13",
|
||||
"first_name" : "first name13"
|
||||
},
|
||||
"birthday" : "1994-07-30",
|
||||
"user_id" : "127b9979-1474-4da1-8453-1e10462ae593",
|
||||
"vote_annonymous" : false,
|
||||
"follow_annonymous" : false,
|
||||
"user" : null,
|
||||
"created_at" : "2019-07-30T14:08:49.742Z"
|
||||
}
|
||||
}"""
|
||||
|
||||
@Test
|
||||
fun testRoute() {
|
||||
withTestApplication({ module() }) {
|
||||
handleRequest(HttpMethod.Get, "/articles").apply {
|
||||
assertEquals(HttpStatusCode.OK, response.status())
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Post, "/articles") {
|
||||
this.setBody(article)
|
||||
this.addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, response.status())
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Get, "/articles/8e8dd0aa-2b2b-41e1-bff5-ea613c988774").apply {
|
||||
assertEquals(HttpStatusCode.OK, response.status())
|
||||
assertTrue(response.content!!.contains("8e8dd0aa-2b2b-41e1-bff5-ea613c988774"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
35
src/test/kotlin/RunCucumberTest.kt
Normal file
35
src/test/kotlin/RunCucumberTest.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
import cucumber.api.CucumberOptions
|
||||
import cucumber.api.Scenario
|
||||
import cucumber.api.java8.En
|
||||
import cucumber.api.junit.Cucumber
|
||||
import feature.Context
|
||||
import fr.dcproject.config
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
import io.ktor.server.testing.createTestEnvironment
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
import feature.Context.Companion.current as contextCurrent
|
||||
|
||||
@RunWith(Cucumber::class)
|
||||
@CucumberOptions(plugin = ["pretty"])
|
||||
class RunCucumberTest: En, KoinTest {
|
||||
private val migrations: Migrations by inject()
|
||||
init {
|
||||
Before(-1) { scenario: Scenario ->
|
||||
config.database = "test"
|
||||
config.username = "test"
|
||||
config.password = "test"
|
||||
contextCurrent = Context(TestApplicationEngine(createTestEnvironment()) {}, scenario)
|
||||
|
||||
migrations.run()
|
||||
}
|
||||
|
||||
After { scenario: Scenario ->
|
||||
migrations.forceAllDown()
|
||||
contextCurrent.engine.stop(0L, 0L, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/test/kotlin/feature/Context.kt
Normal file
44
src/test/kotlin/feature/Context.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package feature
|
||||
|
||||
import cucumber.api.Scenario
|
||||
import fr.dcproject.module
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.server.testing.TestApplicationCall
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
import io.ktor.server.testing.TestApplicationRequest
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
class Context(
|
||||
val engine: TestApplicationEngine,
|
||||
val scenario: Scenario
|
||||
) {
|
||||
companion object {
|
||||
lateinit var current: Context
|
||||
}
|
||||
|
||||
init {
|
||||
engine.start()
|
||||
val moduleFunction: Application.() -> Unit = { module() }
|
||||
val test: TestApplicationEngine.() -> Unit = {
|
||||
moduleFunction(application)
|
||||
}
|
||||
engine.test()
|
||||
}
|
||||
|
||||
var call: TestApplicationCall? = null
|
||||
|
||||
private val requestContextConfigurations: MutableList<TestApplicationRequest.() -> Unit> = mutableListOf()
|
||||
fun setupRequest(testApplicationRequest: TestApplicationRequest) {
|
||||
requestContextConfigurations.forEach {
|
||||
it(testApplicationRequest)
|
||||
}
|
||||
}
|
||||
fun setupNextRequests(requestContextConfiguration: TestApplicationRequest.() -> Unit) = requestContextConfigurations.add(requestContextConfiguration)
|
||||
}
|
||||
|
||||
fun TestApplicationRequest.applyConfigurations() {
|
||||
Context.current.setupRequest(this)
|
||||
}
|
||||
123
src/test/kotlin/feature/Request.kt
Normal file
123
src/test/kotlin/feature/Request.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
package feature
|
||||
|
||||
import com.google.gson.Gson
|
||||
import cucumber.api.java8.En
|
||||
import fr.dcproject.entity.Citizen
|
||||
import fr.dcproject.entity.User
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.cucumber.datatable.DataTable
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.testing.TestApplicationCall
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
import io.ktor.server.testing.setBody
|
||||
import org.joda.time.DateTime
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
import org.opentest4j.AssertionFailedError
|
||||
import java.util.*
|
||||
import kotlin.test.asserter
|
||||
import feature.Context.Companion.current as currentContext
|
||||
|
||||
class Request: En, KoinTest {
|
||||
private val migrations: Migrations by inject()
|
||||
private val requester: Requester by inject()
|
||||
init {
|
||||
// Before { scenario: Scenario ->
|
||||
// migrations.run()
|
||||
// }
|
||||
//
|
||||
// After { scenario: Scenario ->
|
||||
// migrations.forceAllDown()
|
||||
// }
|
||||
|
||||
When("I have citizen:") { body: DataTable ->
|
||||
val user = User(username = "jaque", plainPassword = "azerty")
|
||||
val data = body.asMap<String, String>(String::class.java, String::class.java)
|
||||
val citizen = Citizen(
|
||||
id = UUID.fromString(data["id"]),
|
||||
name = Citizen.Name(data["firstName"], data["lastName"]),
|
||||
birthday = DateTime.now(),
|
||||
user = user
|
||||
)
|
||||
val test: TestApplicationEngine.() -> Unit = {
|
||||
requester
|
||||
.getFunction("insert_user")
|
||||
.selectOne(user)
|
||||
requester
|
||||
.getFunction("upsert_citizen")
|
||||
.selectOne(citizen)
|
||||
}
|
||||
|
||||
currentContext.engine.test()
|
||||
}
|
||||
|
||||
When("I send a {string} request to {string} with body:") { method: String, uri: String, body: String ->
|
||||
val test: TestApplicationEngine.() -> Unit = {
|
||||
currentContext.call = handleRequest {
|
||||
applyConfigurations()
|
||||
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||
this.method = HttpMethod.parse(method)
|
||||
this.uri = uri
|
||||
setBody(body)
|
||||
}
|
||||
}
|
||||
|
||||
currentContext.engine.test()
|
||||
}
|
||||
|
||||
When("I send a {string} request to {string}") { method: String, uri: String ->
|
||||
val test: TestApplicationEngine.() -> Unit = {
|
||||
currentContext.call = handleRequest {
|
||||
applyConfigurations()
|
||||
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||
this.method = HttpMethod.parse(method.toUpperCase())
|
||||
this.uri = uri
|
||||
}
|
||||
}
|
||||
|
||||
currentContext.engine.test()
|
||||
}
|
||||
|
||||
Then("the response status code should be {int}") { statusCode: Int ->
|
||||
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call", statusCode, null)
|
||||
with(call) {
|
||||
assertEquals(HttpStatusCode.fromValue(statusCode), response.status(), response.content)
|
||||
}
|
||||
}
|
||||
|
||||
And("the response should contain:") { expected: DataTable ->
|
||||
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
|
||||
val p = call.response
|
||||
val response = Gson().fromJson<List<Map<String, String>>>(p.content, List::class.java)
|
||||
|
||||
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, value) ->
|
||||
response.forEach {
|
||||
if (it.containsKey(key)) {
|
||||
assertEquals(it[key], value)
|
||||
return@And
|
||||
}
|
||||
}
|
||||
asserter.fail("The response not contain $key field")
|
||||
}
|
||||
}
|
||||
|
||||
And("the response should contain object:") { expected: DataTable ->
|
||||
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
|
||||
val p = call.response
|
||||
val response = Gson().fromJson<Map<String, String>>(p.content, Map::class.java)
|
||||
|
||||
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, value) ->
|
||||
if (response.containsKey(key)) {
|
||||
assertEquals(value, response[key])
|
||||
return@And
|
||||
}
|
||||
asserter.fail("The response not contain $key field")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/test/resources/feature/articles.feature
Normal file
35
src/test/resources/feature/articles.feature
Normal file
@@ -0,0 +1,35 @@
|
||||
Feature: articles routes
|
||||
|
||||
Scenario: The route for get articles must response a 200
|
||||
When I send a "GET" request to "/articles"
|
||||
Then the response status code should be 200
|
||||
|
||||
# Scenario: The route for get article must response a 200
|
||||
# When I send a "GET" request to "/articles/55a24426-139b-4ee7-b1e2-a3d016d66cc2"
|
||||
# Then the response status code should be 200
|
||||
|
||||
Scenario: The route for get article must response a 200
|
||||
Given I have citizen:
|
||||
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||
| firstName | Jaque |
|
||||
| lastName | Dupuis |
|
||||
When I send a "POST" request to "/articles" with body:
|
||||
"""
|
||||
{
|
||||
"version_id": "09c418b6-63ba-448b-b38b-502b41cd500e",
|
||||
"title": "title2",
|
||||
"annonymous": false,
|
||||
"content": "content2",
|
||||
"description": "description2",
|
||||
"tags": [
|
||||
"green"
|
||||
],
|
||||
"created_by": {
|
||||
"id": "64b7b379-2298-43ec-b428-ba134930cabd"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Then the response status code should be 200
|
||||
And the response should contain object:
|
||||
| version_id | 09c418b6-63ba-448b-b38b-502b41cd500e |
|
||||
| title | title2 |
|
||||
Reference in New Issue
Block a user