fixs and move files

This commit is contained in:
2019-08-03 00:57:00 +02:00
parent 63a50dcb92
commit 7c3028eca2
60 changed files with 171 additions and 105 deletions

View File

@@ -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>()) }
}

View File

@@ -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")
}
}
}

View File

@@ -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")
}

View 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) }
}

View File

@@ -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
)
}

View File

@@ -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()

View 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
}

View 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>

View 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;
$$;

View 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;
$$;

View 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;
$$;

View 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;
$$;

View 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;
$$;

View 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;
$$;

View 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;
$$;

View 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;
$$;

View File

@@ -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);

View 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);

View File

@@ -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);

View 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);

View File

@@ -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);

View File

@@ -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);

View 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);

View 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);

View 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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View 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);

View 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);

View File

@@ -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;

View 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);

View 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);

View File

@@ -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);

View 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);

View 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);

View 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";

View 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
);

View 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;
$$;

View 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;
$$;

View 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();

View 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();

View 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();

View 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;
$$;

View 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();

View 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"))
}
}
}
}

View 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)
}
}
}

View 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)
}

View 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")
}
}
}
}

View 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 |