This is an automated email from the git hooks/post-receive script. New commit to branch bow-v2 in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit a10748b308af47c0d4bc3e301cce8eca8860744f Author: Benjamin <poussin@codelutin.com> Date: Wed Jul 31 00:00:36 2019 +0200 script de migration fonctionnel --- src/main/java/com/chorem/bow/BowApp.java | 2 + src/main/java/com/chorem/bow/BowContext.java | 23 + src/main/java/com/chorem/bow/SortOrder.java | 5 + .../com/chorem/bow/model/AuthenticationInfo.java | 2 + src/main/java/com/chorem/bow/model/BowUser.java | 7 +- src/main/java/com/chorem/bow/model/Token.java | 13 + .../bow/repositories/BookmarkRepository.java | 48 +- .../com/chorem/bow/rest/BookmarkResources.java | 67 +- .../resources/db/migration/V1__init_schema.sql | 20 +- .../resources/db/migration/V2__migration_data.sql | 107 +++ src/main/resources/static/css/global.css | 773 +++++++++++++++++++++ src/main/resources/static/img/favicon.png | Bin 0 -> 4267 bytes src/main/templates/views/editUser.rocker.html | 2 +- src/main/templates/views/layout.rocker.html | 106 +++ src/main/templates/views/listUsers.rocker.html | 2 +- src/main/templates/views/main.rocker.html | 12 - src/main/templates/views/search.rocker.html | 72 ++ .../com/chorem/bow/rest/BookmarkResourcesTest.java | 126 ++++ 18 files changed, 1352 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/chorem/bow/BowApp.java b/src/main/java/com/chorem/bow/BowApp.java index 428525b..6010bba 100644 --- a/src/main/java/com/chorem/bow/BowApp.java +++ b/src/main/java/com/chorem/bow/BowApp.java @@ -24,6 +24,8 @@ import java.util.UUID; public class BowApp extends Jooby { { + assets("/static/?*"); + install(new JacksonModule(JacksonModule.create() .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) .setSerializationInclusion(JsonInclude.Include.NON_NULL) diff --git a/src/main/java/com/chorem/bow/BowContext.java b/src/main/java/com/chorem/bow/BowContext.java new file mode 100644 index 0000000..9a87ff8 --- /dev/null +++ b/src/main/java/com/chorem/bow/BowContext.java @@ -0,0 +1,23 @@ +package com.chorem.bow; + +import com.chorem.bow.model.Bookmark; +import com.chorem.bow.model.BowUser; +import org.nuiton.spgeed.Chunk; + +import java.util.LinkedHashSet; + +public class BowContext { + public BowUser user; + public Chunk<Bookmark> result; + public BowSearch search; + + public static class BowSearch { + public LinkedHashSet<String> tags; + public String q; + public SortOrder order; + public long offset; + public long count; + public long total; + } + +} diff --git a/src/main/java/com/chorem/bow/SortOrder.java b/src/main/java/com/chorem/bow/SortOrder.java new file mode 100644 index 0000000..d996f8b --- /dev/null +++ b/src/main/java/com/chorem/bow/SortOrder.java @@ -0,0 +1,5 @@ +package com.chorem.bow; + +public enum SortOrder { + ascVisit, descVisit, ascDate, descDate +} diff --git a/src/main/java/com/chorem/bow/model/AuthenticationInfo.java b/src/main/java/com/chorem/bow/model/AuthenticationInfo.java index 866f273..d088a43 100644 --- a/src/main/java/com/chorem/bow/model/AuthenticationInfo.java +++ b/src/main/java/com/chorem/bow/model/AuthenticationInfo.java @@ -8,6 +8,8 @@ public class AuthenticationInfo { String login; /** html pour créer la forme de login */ String form; + /** domain a utiliser au lieu du domain du site (force le domain, par exemple si le site change de domain) */ + String domain; /** le nombre de composant du domain a prendre (default 2, ex: codelutin.com) */ short domainComponent = 2; short maxLength = 15; diff --git a/src/main/java/com/chorem/bow/model/BowUser.java b/src/main/java/com/chorem/bow/model/BowUser.java index ef1ac04..57cad5f 100644 --- a/src/main/java/com/chorem/bow/model/BowUser.java +++ b/src/main/java/com/chorem/bow/model/BowUser.java @@ -5,6 +5,7 @@ import lombok.Data; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -15,11 +16,7 @@ public class BowUser { Date updateDate; String login; String password; - /** en cle le token, en valeur l'app qui l'utilise (pour quelle raison se token existe */ - Map<String, String> tokens; - LinkedHashSet<String> emails; - /* en cle l'email, en valeur une chaine random (uuid) qui permet la validation */ - Map<String, String> unconfirmedEmails; + List<Token> tokens; AuthenticationInfo authenticationInfo; boolean autoScreenshot; boolean autoFavicon; diff --git a/src/main/java/com/chorem/bow/model/Token.java b/src/main/java/com/chorem/bow/model/Token.java new file mode 100644 index 0000000..5c87f67 --- /dev/null +++ b/src/main/java/com/chorem/bow/model/Token.java @@ -0,0 +1,13 @@ +package com.chorem.bow.model; + +import lombok.Data; + +import java.util.Date; +import java.util.UUID; + +@Data +public class Token { + String name; + UUID token; + Date validity; +} diff --git a/src/main/java/com/chorem/bow/repositories/BookmarkRepository.java b/src/main/java/com/chorem/bow/repositories/BookmarkRepository.java index 877bad8..8e78f99 100644 --- a/src/main/java/com/chorem/bow/repositories/BookmarkRepository.java +++ b/src/main/java/com/chorem/bow/repositories/BookmarkRepository.java @@ -1,17 +1,35 @@ package com.chorem.bow.repositories; +import com.chorem.bow.BowContext; import com.chorem.bow.model.AuthenticationInfo; import com.chorem.bow.model.Bookmark; import com.chorem.bow.model.BowUser; +import org.nuiton.spgeed.Chunk; import org.nuiton.spgeed.annotations.Select; import org.nuiton.spgeed.annotations.Update; +import org.nuiton.spgeed.query.QueryFacet; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.UUID; public interface BookmarkRepository { + @Update(sql = + "INSERT INTO bookmark AS t" + + " SELECT * FROM json_populate_record(NULL::bookmark, ${bookmark |json()}::json)" + + " ON CONFLICT (id) DO UPDATE SET" + + " owner = EXCLUDED.owner," + + " uri = EXCLUDED.uri," + + " description = EXCLUDED.description," + + " privateAlias = EXCLUDED.privateAlias," + + " publicAlias = EXCLUDED.publicAlias," + + " authenticationInfo = EXCLUDED.authenticationInfo," + + " lang = EXCLUDED.lang" + + " RETURNING *") + Bookmark upsert(BowUser user); + @Update(sql = "INSERT INTO bookmark AS t" + " SELECT * FROM json_populate_record(NULL::bookmark, ${bookmark |json()}::json)" + @@ -21,8 +39,9 @@ public interface BookmarkRepository { @Update(sql = "UPDATE bookmark AS t" + " SET (owner, uri, description, privateAlias, publicAlias, authenticationInfo, lang) = " + - " (SELECT * FROM json_populate_record(NULL::bookmark, ${bookmark |json()}::json))" + - " RETURNING *") + " (SELECT owner, uri, description, privateAlias, publicAlias, authenticationInfo, lang" + + " FROM json_populate_record(NULL::bookmark, ${bookmark |json()}::json))" + + " RETURNING *") Bookmark update(Bookmark bookmark); @Update(sql = "UPDATE bookmark AS t" + @@ -55,8 +74,29 @@ public interface BookmarkRepository { @Select(sql = "select * from bookmark where id=${uuid}::uuid") Bookmark find(UUID uuid); - @Select(sql = "select * from bookmark where tags && ${tags | array()}") - List<Bookmark> findAll(List<String> tags); + @Update(sql = "DELETE FROM bookmark WHERE id=${id} RETURNING *") + Bookmark delete(UUID id); + + /** la 1ere comparaison tags est faite pour que le tableau vide (pas de tag demande) retourne true */ + @Select(sql = "select * from bookmark where ${tags.empty | toString()} OR tags && ${tags | array('citext')}") + Chunk<Bookmark> find(Chunk<Bookmark> chunk, Collection<String> tags, String q, QueryFacet... facet); + + String a = "with filtered as (select * from bookmark where tags && ARRAY['facet'::citext] ) select to_json(array_agg(to_jsonb(g))) as result, (select to_json(array_agg(to_jsonb(facet))) from (select unnest(tags) as value, count(*) as count from filtered group by value order by count desc, value limit 4) facet) as facet from (select * from filtered limit 1) g;"; + String b = "with filtered as (select * from bookmark where tags && ARRAY['facet'::citext] ), tags as (select unnest(tags) as value, count(*) as count from filtered group by value order by count desc, value limit 4), uri as (select uri as value, count(*) as count from filtered group by value order by count desc, value limit 4) select to_json(array_agg(to_jsonb(g))) as result, (select jsonb_object_agg(facet, topics) from (select 'tags' as facet, jsonb_object_agg(value, count) as topics [...] + String c = "with filtered as (select * from bookmark where tags && ARRAY['facet'::citext] ), visit as(select r.range as value, count(f.*) as count from (SELECT (ten*5)::text||'-'||(ten*5+4)::text AS range, ten*5 AS r_min, ten*5+4 AS r_max FROM generate_series(0,(SELECT max(visit)/5 FROM filtered) ) AS ten) r left join filtered f on f.visit between r.r_min and r.r_max group by value order by count desc, value limit 4), tags as (select unnest(tags) as value, count(*) as count from fil [...] + + String d ="with w as (select id, fieldname, coalesce(numbervalue::text,datevalue::text,textvalue::text,booleanvalue::text) as value from wikitty_data where id='62559412-0d3e-4aaf-9f0f-ecce93f3e57f'), tags as (select * from w where fieldname like 'Bookmark.tags[%'), o as (select jsonb_set(jsonb_object_agg(fieldname, value), '{id}', to_jsonb(id)) as __json from w group by id), a as (select jsonb_set('[]', '{0}', __json) as __json from o) select * from o, jsonb_to_record(__json) as r1( [...] + String e ="with w as (select id, fieldname, coalesce(numbervalue::text,datevalue::text,textvalue::text,booleanvalue::text) as value from wikitty_data where id='62559412-0d3e-4aaf-9f0f-ecce93f3e57f'), tags as (select id, jsonb_set('{}', '{tags}', jsonb_agg(value)) as __json from w where fieldname like 'Bookmark.tags[%' group by id), o as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json from w where fieldname not like 'Bookmark.tags[%' group by i [...] + + String f ="id | fieldname | numbervalue | datevalue | textvalue | booleanvalue | binaryvalue"; + // requete de transformation de wikity en spgeed + String g = "with w as (select id, fieldname, coalesce(numbervalue::text,datevalue::text,textvalue::text,booleanvalue::text) as value from wikitty_data, tags as (select id, array_agg(value) as tags from w where fieldname like 'Bookmark.tags[%' or fieldname like 'WikittyLabel.labels[%' group by id), nottags_json as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json from w where fieldname not like 'Bookmark.tags[%' and fieldname not like 'WikittyLab [...] + String h = "with w as (select id, fieldname, coalesce(numbervalue::text,datevalue::text,textvalue::text,booleanvalue::text) as value from wikitty_data), tags as (select id, array_agg(value) as tags from w where fieldname like 'Bookmark.tags[%' or fieldname like 'WikittyLabel.labels[%' group by id), nottags_json as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json from w where fieldname not like 'Bookmark.tags[%' and fieldname not like 'WikittyLa [...] + + String i = "with w as (select id, fieldname, coalesce(numbervalue::text,datevalue::text,textvalue::text,booleanvalue::text) as value from wikitty_data where id in (select id from wikitty_admin where extension_list like '%Bookmark%')), tags as (select id, array_agg(value) as tags from w where fieldname like 'Bookmark.tags[%' or fieldname like 'WikittyLabel.labels[%' group by id), nottags_json as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json f [...] + // select * from wikitty_data where id='147e923b-3921-4b17-a88c-43495b477a8a'; +// id, owner, uri, description, tags, creationDate, updateDate, importDate, privateAlias, publicAlias, authenticationInfo, favicon, screenshot, visit +// id UUID, owner UUID, uri TEXT, description TEXT, tags citext[], creationDate TIMESTAMP, updateDate timestamp DEFAULT current_timestamp, importDate TIMESTAMP,privateAlias TEXT[], publicAlias TEXT[],authenticationInfo AuthenticationInfo, favicon BYTEA,screenshot BYTEA, visit INTEGER } diff --git a/src/main/java/com/chorem/bow/rest/BookmarkResources.java b/src/main/java/com/chorem/bow/rest/BookmarkResources.java index 24b023a..047aae8 100644 --- a/src/main/java/com/chorem/bow/rest/BookmarkResources.java +++ b/src/main/java/com/chorem/bow/rest/BookmarkResources.java @@ -1,33 +1,94 @@ package com.chorem.bow.rest; +import com.chorem.bow.model.Bookmark; +import com.chorem.bow.repositories.BookmarkRepository; import com.chorem.bow.repositories.BookmarkRepository; import io.jooby.Jooby; +import io.jooby.StatusCode; +import org.nuiton.spgeed.Chunk; import org.nuiton.spgeed.SqlSession; +import org.nuiton.spgeed.query.QueryFacet; +import org.nuiton.spgeed.query.QueryFacetValue; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.UUID; +import static org.nuiton.spgeed.Chunk.restart; + public class BookmarkResources { //extends Jooby { { // use("/", this); } + protected QueryFacet createFacet(int max) { + QueryFacetValue result = new QueryFacetValue("tags", "unnest(tags)"); + result.setMax(max); + return result; + } + public void use(String path, Jooby app){ app.path(path, () -> { app.get("/", ctx -> { + List<String> tags = ctx.query("tag").toList(); + String q = ctx.query("q").value(""); + int fetch = ctx.query("fetch").intValue(100); + int next = ctx.query("next").intValue(0); + try (SqlSession session = app.require(SqlSession.class)) { BookmarkRepository repo = session.getDao(BookmarkRepository.class); - return repo.findAll(); + Chunk<Bookmark> chunk = repo.find(restart(fetch, next), tags, q, createFacet(50)); + return chunk; } }); app.get("/{id}", ctx -> { - String uuid = ctx.path("id").value(); + UUID id = ctx.path("id").to(UUID.class); + try (SqlSession session = app.require(SqlSession.class)) { + BookmarkRepository repo = session.getDao(BookmarkRepository.class); + Bookmark bookmark = repo.find(id); + if (bookmark == null) { + ctx.setResponseCode(StatusCode.NOT_FOUND); + } + return bookmark; + } + }); + + app.post("/", ctx -> { + Bookmark bookmark = ctx.body(Bookmark.class); + app.getLog().debug("insert bookmark: " + bookmark); try (SqlSession session = app.require(SqlSession.class)) { BookmarkRepository repo = session.getDao(BookmarkRepository.class); - return repo.find(UUID.fromString(uuid)); + return repo.insert(bookmark); } }); + + app.put("/{id}", ctx -> { + UUID id = ctx.path("id").to(UUID.class); + Bookmark bookmark = ctx.body(Bookmark.class); + bookmark.setId(id); + try (SqlSession session = app.require(SqlSession.class)) { + BookmarkRepository repo = session.getDao(BookmarkRepository.class); + return repo.update(bookmark); + } + }); + + app.delete("/{id}", ctx -> { + UUID id = ctx.path("id").to(UUID.class); + try (SqlSession session = app.require(SqlSession.class)) { + BookmarkRepository repo = session.getDao(BookmarkRepository.class); + Bookmark bookmark = repo.delete(id); + if (bookmark == null) { + ctx.setResponseCode(StatusCode.NOT_FOUND); + } + return bookmark; + } + }); + + + }); } } diff --git a/src/main/resources/db/migration/V1__init_schema.sql b/src/main/resources/db/migration/V1__init_schema.sql index 3b35ed1..a986aa9 100644 --- a/src/main/resources/db/migration/V1__init_schema.sql +++ b/src/main/resources/db/migration/V1__init_schema.sql @@ -12,10 +12,17 @@ CREATE OR REPLACE FUNCTION text(citext[]) returns text language sql immutable as $$ select $1::text $$; +create type Token as ( + name TEXT, + token UUID, + validity timestamp +); + create type AuthenticationInfo as ( description text, login text, form text, + domain text, -- force le domain (pour ne pas utiliser celui du site) domainComponent smallint, -- le nombre de composant du domain a prendre (default 2, ex: codelutin.com) maxLength smallint, allowedChar text, @@ -36,22 +43,17 @@ create table bowUser ( updateDate timestamp DEFAULT current_timestamp, login Text, password Text, - tokens jsonb, -- en cle le token, en valeur l'app qui l'utilise - emails TEXT[], - unconfirmedEmails jsonb, -- en cle l'email, en valeur l'uuid qui permet la validation - authenticationInfo AuthenticationInfo, + tokens jsonb, -- Token[], + authenticationInfo jsonb, -- AuthenticationInfo, autoScreenshot boolean, autoFavicon boolean, maxTagInCloud smallint, maxResult smallint, - actions Action[] --- ,EXCLUDE USING gist (emails WITH &&, unconfirmedEmails WITH &&) + actions jsonb -- Action[] ); CREATE UNIQUE INDEX bowUser_login_idx ON BowUser (login); CREATE INDEX bowUser_token_idx ON BowUser USING gin (tokens); -CREATE INDEX bowUser_emails_idx ON BowUser USING gin (emails); -CREATE INDEX bowUser_unconfirmedEmails_idx ON BowUser USING gin (unconfirmedEmails); create table bookmark ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -64,7 +66,7 @@ create table bookmark ( importDate TIMESTAMP, privateAlias TEXT[], publicAlias TEXT[], - authenticationInfo AuthenticationInfo, + authenticationInfo jsonb, -- AuthenticationInfo, favicon BYTEA, screenshot BYTEA, visit INTEGER, diff --git a/src/main/resources/db/migration/V2__migration_data.sql b/src/main/resources/db/migration/V2__migration_data.sql new file mode 100644 index 0000000..fe45b8a --- /dev/null +++ b/src/main/resources/db/migration/V2__migration_data.sql @@ -0,0 +1,107 @@ +-- to keep creation date +ALTER TABLE BowUser DISABLE TRIGGER update_BowUser_createDate; +ALTER TABLE Bookmark DISABLE TRIGGER update_Bookmark_createDate; + +-- migration users +with + -- all values field in single field + w as (select id, fieldname, coalesce(numbervalue::text, datevalue::text, textvalue::text, booleanvalue::text, binaryvalue::text) as value from wikitty_data where id in (select id from wikitty_admin where extension_list like '%User%')) + -- all action (aka bowSearchPrefix) in json with bad bey +, all_actions_badjson as (select jsonb_object_agg(fieldname, coalesce(numbervalue::text, datevalue::text, textvalue::text, booleanvalue::text, binaryvalue::text)) as __json from wikitty_data where fieldname like 'BowSearchPrefix.%' group by id) + -- all action +, all_actions as (select __json->>'BowSearchPrefix.bowUser' as id, jsonb_agg(jsonb_build_object( + 'prefix', __json->>'BowSearchPrefix.prefix', + 'action', __json->>'BowSearchPrefix.search', + 'suggest', __json->>'BowSearchPrefix.suggestion') + ) as actions from all_actions_badjson group by id) + -- all authenticationInfo in json with bad key +, all_authenticationInfo_badjson as (select jsonb_object_agg(fieldname, coalesce(numbervalue::text, datevalue::text, textvalue::text, booleanvalue::text, binaryvalue::text)) as __json from wikitty_data where fieldname like 'BowAuthentication.%' group by id) + -- all action +, all_authenticationInfo as (select __json->>'BowAuthentication.target' as id, jsonb_build_object( + 'description', __json->>'BowAuthentication.description', + 'login', __json->>'BowAuthentication.login', + 'form', __json->>'BowAuthentication.form', + 'domain', __json->>'BowAuthentication.domain', + 'domaincomponent', 3, + 'maxlength', __json->>'BowAuthentication.maxLength', + 'allowedchar', __json->>'BowAuthentication.include', + 'disallowedchar', __json->>'BowAuthentication.exclude', + 'salt', __json->>'BowAuthentication.prefix', + 'suffix', __json->>'BowAuthentication.suffix' + ) as authenticationInfo from all_authenticationInfo_badjson) + -- convert all others field to json with bad key +, badjson as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json from w where fieldname not like 'Bookmark.tags[%' and fieldname not like 'WikittyLabel.labels[%' group by id) + -- extract field from json to normalize name +, all_json as (select n.id, jsonb_build_object('id', n.id, + 'creationdate', coalesce(__json->>'WikittyToken.date'::text, now()::text), + 'login', __json->>'WikittyUser.login', + 'password', __json->>'WikittyUser.password', + 'tokens', json_build_array(jsonb_build_object('token', __json->>'BowUser.permanentToken', 'name', 'initial token')), + 'autoscreenshot', __json->>'BowPreference.screenshot', + 'autofavicon', __json->>'BowPreference.favicon', + 'maxtagincloud', __json->>'BowPreference.tags', + 'maxresult', __json->>'BowPreference.bookmarks', + 'actions', actions, + 'authenticationinfo', authenticationInfo) as __json + from badjson n left join all_actions t on n.id=t.id left join all_authenticationInfo a on n.id=a.id) +--, all_json as (select row_to_json((id, creationDate, login, password, tokens, authenticationInfo, autoScreenshot, autoFavicon, maxTagInCloud, maxResult, actions)) as __json from final) +-- insert bookmarks in new table +insert into bowUser select j.* from all_json, jsonb_populate_record(NULL::bowUser, __json) as j ON CONFLICT (login) DO NOTHING ; +--insert into bowUser (id, creationDate, login, password, tokens, authenticationInfo, autoScreenshot, autoFavicon, maxTagInCloud, maxResult, actions) select id::uuid, creationDate::TIMESTAMP, login::TEXT, password::TEXT, tokens::jsonb, authenticationInfo::jsonb, autoScreenshot::boolean, autoFavicon::boolean, maxTagInCloud::smallint, maxResult::smallint, actions::jsonb[] from final; + + + + + + + +-- migration bookmarks +with + -- all values field in single field + w as (select id, fieldname, coalesce(numbervalue::text, datevalue::text, textvalue::text, booleanvalue::text, binaryvalue::text) as value from wikitty_data where id in (select id from wikitty_admin where extension_list like '%Bookmark%')) + -- convert tags/labels to text array +, tags as (select id, jsonb_agg(value) as tags from w where fieldname like 'Bookmark.tags[%' or fieldname like 'WikittyLabel.labels[%' group by id) + -- all authenticationInfo in json with bad key +, all_authenticationInfo_badjson as (select jsonb_object_agg(fieldname, coalesce(numbervalue::text, datevalue::text, textvalue::text, booleanvalue::text, binaryvalue::text)) as __json from wikitty_data where fieldname like 'BowAuthentication.%' group by id) + -- all action +, all_authenticationInfo as (select __json->>'BowAuthentication.target' as id, jsonb_build_object( + 'description', __json->>'BowAuthentication.description', + 'login', __json->>'BowAuthentication.login', + 'form', __json->>'BowAuthentication.form', + 'domain', __json->>'BowAuthentication.domain', + 'domaincomponent', 3, + 'maxlength', __json->>'BowAuthentication.maxLength', + 'allowedchar', __json->>'BowAuthentication.include', + 'disallowedchar', __json->>'BowAuthentication.exclude', + 'salt', __json->>'BowAuthentication.prefix', + 'suffix', __json->>'BowAuthentication.suffix' + ) as authenticationInfo from all_authenticationInfo_badjson) + -- convert all others field to json +, nottags_json as (select id, jsonb_build_object('id', id) || jsonb_object_agg(fieldname, value) as __json from w where fieldname not like 'Bookmark.tags[%' and fieldname not like 'WikittyLabel.labels[%' group by id) + -- extract field from json to normalize name +, all_json as (select n.id, jsonb_build_object('id', n.id, + 'owner', coalesce( __json->>'BowBookmark.bowUser',__json->>'WikittyAuthorisation.owner'), + 'uri', coalesce(__json->>'Bookmark.link', __json ->>'BowBookmark.link'), + 'description', coalesce(__json->>'Bookmark.description', __json->>'BowBookmark.description'), + 'creationdate', coalesce(__json->>'WikittyToken.date', __json->>'BowBookmark.creationDate'), + 'importdate', coalesce(__json->>'Import.date', __json->>'BowImport.importDate' ), + 'privatealias', json_build_array(coalesce(__json->>'Bookmark.alias', __json->>'BowBookmark.privateAlias')), + 'publicalias', json_build_array(__json->>'BowBookmark.publicAlias'), + 'favicon', __json->>'BowBookmark.favicon', + 'screenshot', __json->>'BowBookmark.screenshot', + 'visit', coalesce(__json->>'Bookmark.click', __json->>'BowBookmark.click'), + 'tags', tags, + 'authenticationinfo', authenticationInfo, + 'lang', 'english') as __json + from nottags_json n left join tags t on n.id=t.id left join all_authenticationInfo a on n.id=a.id) + +-- insert bookmarks in new table +insert into bookmark select j.* from all_json, jsonb_populate_record(NULL::bookmark, __json) as j inner join bowUser b on b.id=j.owner ; -- ON CONFLICT (login) DO NOTHING ; + +update bowUser set creationDate = (select min(creationDate) from bookmark where owner=bowUser.id); + +-- reactivate trigger +ALTER TABLE BowUser ENABLE TRIGGER update_BowUser_createDate; +ALTER TABLE Bookmark ENABLE TRIGGER update_Bookmark_createDate; + +-- bookmark.id,bookmark.owner, bookmark.uri, bookmark.tags, bookmark.creationdate, bookmark.updatedate,bookmark.importdate, bookmark.privatealias, bookmark.publicalias, bookmark.visit, bookmark.lang diff --git a/src/main/resources/static/css/global.css b/src/main/resources/static/css/global.css new file mode 100644 index 0000000..51a6feb --- /dev/null +++ b/src/main/resources/static/css/global.css @@ -0,0 +1,773 @@ +@charset "utf-8"; +/* + * #%L + * BOW UI + * %% + * Copyright (C) 2010 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% +*/ + +*{ + padding:0; + margin:0; +} + +body{ + font-size:10px; + font-family:Verdana, Arial, Helvetica, sans-serif; +} + +.clearfix{ + height:1%; +} + +.clearfix:after{ + content:"."; + height:0; + line-height:0; + display:block; + visibility:hidden; + clear:both; +} + +.left{ + float:left; +} + +.right{ + float:right; +} + +.recherche{ + padding-top:20px; + padding-right:15%; +} +/* +#header .input{ + width:250px; +} +*/ +.button{ + margin-top:20px; + background:url('../img/fdboutonV.jpg') repeat-x; + height:31px; + line-height:31px; + color:#FFFFFF; + font-weight:bold; + border:none; + width:auto; + padding:2px; +} + +textarea{ + height:100px; + width:200px; +} + +#wrap{ + clear:both; + float:left; + overflow-x:hidden; + overflow-y:visible; + position:relative; + width:100%; + background-color:#9EDCF8; +} + +#main .menu{ + width:90%; + height:35px; + background:#804561 url('../img/pointemenu.jpg') no-repeat right; + margin-bottom:15px; + padding:0 40px; + line-height:35px; + clear:both; +} + +#main div[class="menu clearfix"] h2{ + color:#9edcf8; + font-size:18px; + font-weight:normal; + float:left; +} + +#main .menu form{ + float:right; + color:#9edcf8; + font-size:12px; +} + +#import .input input{ + width:90%; + margin-bottom:10px; +} + +.error{ + float:left; + color:red; + font-size:medium; + background-color:white; + overflow:auto; + margin-bottom:25px; + padding:4px; +} + +#footer{ + background-color:#804561; + padding-top:30px; +} + +#footer a{ + color:#bf8a9c; +} + +#footer p{ + font-size:14px; + text-align:center; + line-height:50px; +} + +.errorMessage{ + font-weight:normal; + font-size:12px; + color:red; +} + +#actionmessageHeader{ + color:blue; + position:fixed; + left:25%; + top:12px; +} + +#actionerrorHeader{ + color:red; + position:relative; + left:25%; + top:12px; +} + +#actionmessageHeader ul, #actionerrorHeader ul{ + list-style-type:none; +} + +.fond { + -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dbdbdb'); + background-image: -moz-linear-gradient(top, #fff, #dbdbdb); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#dbdbdb)); + + -webkit-border-radius: 0.5em; + -moz-border-radius: 0.5em; + + padding:10px; +} + +form, .wwFormTable{ + color:#804561; + font-size:11px; + font-weight:bold; +} + + +form.pretty-form>input, form.pretty-form>textarea, form.pretty-form>select { + display: block; + width: 100%; + font-family: Helvetica, sans-serif; + font-size: 1.4em; + margin: 0px 0px 10px 0px; +} + +form.pretty-form>label { + display: block; + text-align: left; + margin-right: 15px; + width: 100%; +} + +form.pretty-form textarea:focus, form.pretty-form input:focus, form.pretty-form select:focus { + border:1px solid #666; + background:#e3f1f1; +} + +form.pretty-form input.submit-button { + width: 100px; + float: right; + margin: 10px; +} + +.public-true .group-name { + background-color: darkgray; +} + +a.scriptlet { + background:url('../img/scriptlet.png') center left no-repeat; + padding-left: 24px; + padding-right: 3px; +} + +a.script { + background:url('../img/terminal.png') center left no-repeat; + padding-left: 24px; + padding-right: 3px; +} + +#page{ + background-color:#9EDCF8; + float:left; + left:77%; + position:relative; + width:100%; + margin-bottom:50px; +} + +#header{ + background:#fff url('../img/fondhead.jpg') repeat-x 0 0; + float:left; + position:absolute; + right:85%; + top:0; + height:100px; + width:100%; +} + +#header a.logo{ + background:transparent url('../img/logobow.jpg') repeat scroll 0 0; + display:block; + height:100px; + text-indent:-99999px; + width:290px; + float:left; + left:8%; + position:relative; +} + +#main{ + margin-top:120px; + float:left; + position:relative; + right:69%; + width:59%; + clear:both; +} + +.content{ + width:100%; + float:left; + background-color:#fff; + margin-bottom: 25px; +} + +#side { + clear:right; + float:right; + position:relative; + right:73%; + width:27%; +} + +.bookmark{ + width:100%; + height:auto; + word-wrap:break-word; +} + +.spacemax { + width: 100%; +} + +.bookmark .favicon { + width: 32px; + height: 32px; + float:left; +} + +.bookmark .bookmarkhead{ + width:100%; + height:32px; + background-color:#bf8a9c; + border-bottom: 0 solid #9EDCF8; + position:relative; + clear:both; +} + +.bookmark .bookmarkcontenu{ + clear:both; + padding-top:5px; + padding-right: 152px; +} + +.bookmark .bookmarkhead .date{ + color:#fff; + font-size:10px; + float:left; + margin-top:10px; +} + +.bookmark .bookmarkhead .screenshotLink{ + position:absolute; + margin-top: 10px; + top:0; + right:96px; + width:31px; + height:32px; + background:url('../img/camera.png') no-repeat; +} + +.bookmark .bookmarkhead .edit-authentication{ + background:url('../img/cadena.png') center no-repeat; + width:31px; + height:32px; + position:absolute; + top:0; + right:64px; +} + +.bookmark .bookmarkhead .edit{ + background:url('../img/edit.jpg') no-repeat; + width:31px; + height:32px; + position:absolute; + top:0; + right:32px; +} + +.bookmark .bookmarkhead .supprim{ + background:url('../img/croix.jpg') no-repeat; + width:31px; + height:32px; + position:absolute; + top:0; + right:0; +} + +.bookmark .bookmarkhead a{ + color:#FFF; + font-weight:bold; + font-size:14px; + line-height:32px; + /*padding-left:40px;*/ + /*background:url('../img/ptit-livre.jpg') no-repeat;*/ + height:32px; + display:block; + text-decoration:none; + float:left; + margin-right:3%; + margin-left:3%; +} + +.bookmark .bookmarkhead .alias{ + float:left; + margin-right:15px; + display:block; + /* background:url('../img/ptit-livre.jpg') no-repeat; */ +} + +.bookmark .screenshot{ + float:left; + margin:5px 10px; + width:100px; + height:75px; + /*background:url('../img/livreG.jpg') no-repeat;*/ +} + +.bookmark .description{ + float:left; + margin:5px 5px; + color:#999999; + font-size:12px; + position: relative; + width: 100%; + top:-80px; + left: 100px; +} + +.bookmark .description h3 { + color:#88516c; +} +.markdown p { + margin: inherit; +} + +.markdown p, .markdown ul, .markdown ol { + margin-bottom: 5px; +} + +.markdown ul { + margin-left: 20px; +} + +.bookmark .description .tags{ + color:#88516c; + background:url('../img/tag.jpg') no-repeat left center; + font-size:12px; + padding-left:30px; + height:auto; + width:100%; + line-height:28px; + padding-top:10px; + margin-bottom:-70px; +} + +.bookmark .description .tags .tag{ + margin-right:5px; + text-decoration:none; +} + +.bookmark .click{ + background:transparent url('../img/click.jpg') no-repeat scroll 0 0; + float:right; + height:27px; + margin:10px -25px 5px 5px; + padding-top:31px; + position:relative; + right:-120px; + width:31px; + text-align:center; +} + +.nobookmarks{ + font-size: large; + padding-left: 5px; +} + +#logoutDiv{ + float:right; + position:relative; + right:67%; + width:33%; + background:#BF8A9C url('../img/moyen-livre.jpg') no-repeat scroll 7% 44%; + height:100px; +} + +#logoutDiv a.help{ + background:transparent url('../img/aide.jpg') no-repeat scroll 0 0; + display:block; + height:34px; + text-indent:-99999px; + width:34px; + margin-left:15%; + margin-top:12%; + float:left; +} + +#logoutDiv form input{ + background:transparent url('../img/fondbouton.jpg') repeat-x scroll 0 0; + border:medium none; + color:#9C7186; + font-size:12px; + font-weight:bold; + height:27px; + line-height:27px; + display:block; + text-decoration:none; + text-align:center; + width:100px; + float:left; + margin-left:25%; + margin-top:6%; +} + +#side #colonneD{ + font-size:14px; + color:#9edcf8; +} + +#colonneD{ + overflow-x:auto; + background-color:#804561; + margin-top: -2px; +} + +#colonneD a{ + font-size:12px; + color:#9edcf8; +} + +#colonneD ul.droite{ + padding:5% 10%; + border-bottom: 0 solid #9edcf8; +} + +#colonneD ul.droite li{ + list-style:none; + height:auto; + font-size:100%; +} + +#colonneD ul.droite li a{ + font-size:14px; + font-weight:normal; + text-decoration:none; + color:#9edcf8; +} + +#colonneD ul.droite li a:hover{ + text-decoration:underline; +} + +#colonneD #extensions h2 { + margin: 4% 0 0 10%; +} + +#colonneD #extensions li { + padding-bottom: 10px; +} + +#colonneD #extensions .extensionIcon { + float:left; + border:0 none; + margin-right:10px; +} + +#colonneD #extensions .extensionName { + padding: 4px 0 0 30px; +} + +#colonneD #nuage, +#colonneD #import, +#colonneD #add{ + padding:4% 10%; + border-bottom: 0 solid #9edcf8; +} + +#colonneD h2{ + color:#9edcf8; + font-size:140%; + font-weight:normal; +} + +#colonneD #add{ + height:auto; +} + +#colonneD #add form{ + clear:both; +} + +#colonneD form .input{ + padding:10px 0; +} + +#colonneD form input{ + width:50%; + border:none; + height:28px; + padding-left:4px; +} + +#colonneD #add form label{ + display:inline-block; + padding-right:9%; + text-align:right; + width:45px; +} + +#colonneD form input[type="submit"]{ + background:transparent url('../img/fondbouton.jpg') repeat-x scroll 0 0; + border:medium none; + color:#9C7186; + display:block; + float:right; + font-size:12px; + font-weight:bold; + height:28px; + line-height:28px; + position:relative; + right:20%; + text-align:center; + text-decoration:none; + width:25%; +} + +#colonneD #nuage{ + padding-right:80px; + min-height:100px; + text-align:justify; +} + +.colonnebas img{ + width:86%; + height:auto; + display:block; + position:relative; +} + +#import .input input{ + width:90%; + margin-bottom:10px; +} + + + +.formFrame{ + float: left; /* pour que le contenu en float ne deborde pas du cadre */ + width: 90%; + min-width:400px; +/* min-height: 315px; */ + position:relative; + margin-top:20px; + margin-bottom:20px; + margin-right:100px; + margin-left:40px; + padding-top:10px; + padding-bottom:10px; + padding-left:40px; + padding-right:70px; +} + +.formFrame ul{ + padding-left:10px; +} + +.formFrame h1{ + color:#804561; + width:225px; + position:relative; + margin:20px auto; +} + +.formFrame p{ + color:#804561; + font-size:11px; + font-weight:bold; + padding:15px 0; +} + +.formFrame p input[type="text"], td input[type="text"], +.formFrame p input[type="password"], td input[type="password"]{ + width:225px; +} + +.formFrame input[type="submit"]{ + margin-top:20px; + background:url('../img/fdboutonV.jpg') repeat-x; + height:31px; + line-height:31px; + color:#FFFFFF; + font-weight:bold; + border:none; + width:auto; + padding:2px; +} + +.formFrame p a{ + position:absolute; + color:#804561; + left:75px; + font-weight:bold; + font-size:12px; + margin-top:2px; +} + +.formFrame #homePage, +.formFrame #regenPermToken{ + bottom:50px; + left:185px; + font-size:10px; +} + +.formFrame p input, td input{ + margin-bottom:2px; +} + +.deleteImport{ + width:100%; + height:32px; + border:1px solid black; + margin-bottom:4px; + padding-top:10px; + padding-left:15px; +} + +.deleteImportPink{ + background-color:#BF8A9C; +} + +.deleteImportWhite{ + background-color:white; + margin-left:30px; +} + +.deleteImport span{ + color:blue; + font-size:large; +} + +.deleteImport .deleteImportButton{ + background:url('../img/croixtr.png') no-repeat; + width:31px; + height:32px; + float:right; + margin-right:15px; + position:relative; + top:-5px; +} + +#deleteSearchResultsButton{ + background:url('../img/croixtr.png') no-repeat; + width:31px; + height:32px; + float:right; + margin-left:25px; + margin-right:11px; +} + +.input label{ + color:#804561; +/* + font-size:20px; + font-weight:normal; + font-style:normal; + float:left; + margin-bottom:20px; + width:100%; +*/ +} + +#bookmarkForm label{ + margin-top:12px; + font-style:normal; +} + +#add h2{ + margin-bottom:20px; +} + +#adminActions li{ + list-style-type:none; + padding-bottom:15px; +} + +#adminActions li a{ + text-decoration:none; + color:#804561; + font-size:11px; + font-weight:bold; +} + +#labelsForm .list input{ + float: left; + clear:left; +} + +#labelsForm .list label { + float: left; +} + +#labelsForm .action { + float: left; + clear:left; +} diff --git a/src/main/resources/static/img/favicon.png b/src/main/resources/static/img/favicon.png new file mode 100644 index 0000000..49de5d9 Binary files /dev/null and b/src/main/resources/static/img/favicon.png differ diff --git a/src/main/templates/views/editUser.rocker.html b/src/main/templates/views/editUser.rocker.html index 76476fd..77b3ff0 100644 --- a/src/main/templates/views/editUser.rocker.html +++ b/src/main/templates/views/editUser.rocker.html @@ -2,7 +2,7 @@ @args (BowUser user) -@views.main.template("Edit user", RockerContent.NONE, RockerContent.NONE) -> { +@views.layout.template("Edit user", RockerContent.NONE, RockerContent.NONE) -> { <form method="post"> <input type="hidden" name="id" value="@?user.getId()"> login: <input type="text" name="login" value="@?user.getLogin()"><br> diff --git a/src/main/templates/views/layout.rocker.html b/src/main/templates/views/layout.rocker.html new file mode 100644 index 0000000..1f4406d --- /dev/null +++ b/src/main/templates/views/layout.rocker.html @@ -0,0 +1,106 @@ +@import com.chorem.bow.BowContext + +@args (BowContext ctx, String title, RockerContent extracss, RockerContent extrajs, RockerBody content) + +<!-- + #%L + BOW UI + %% + Copyright (C) 2010 - 2011 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> + +<html> +<head> + <meta charset="utf-8"> + <title>Bow : @title</title> + @extracss + <meta name="description" content="Bookmarks on the web" /> + <link rel="icon" type="image/png" href="/static/img/favicon.png" /> + <link rel="search" type="application/opensearchdescription+xml" title="Bow" href="/opensearch.xml" /> + + <link href="/static/css/global.css" rel="stylesheet" type="text/css" media="all" /> +</head> +<body id="page-home"> +<div id="header"> + <div> + <a href="/bow/home.action" class="logo">bow</a> + </div> + <div class="recherche right"> + <h3>Recherche</h3> + <div class="input"> + <form action="/search" method="get"> + tags: <input type="text" name="tags" value=""/> + fulltext: <input type="text" name="q" value=""/> + <input type="submit" value="search" name="submit"/> + </form> + </div> + <div class="input"> + <form action="/search" method="post"> + <input type="text" name="q" value=""/> + <input type="submit" value="Web" name="submit"/> + </form> + </div> + </div> + + <div id="logoutDiv"> + @ctx.user.login + <form id="logout" action="/logout" method="post"> + <input type="submit" value="Logout" id="logout_submit" name="submit"/> + </form> + </div> + + <div id="help"> + <a href="http://doc.bookmarks.cl/" class="help" target="_blank">Aide</a> + </div> +</div> + +<div id="main"> + @content + @extrajs +</div> + +<div id="menu"> + <ul class="droite"> + <li><a href="/admin">Admin</a></li> + <li><a href="/preferences">Préférences</a></li> + <li><a href="/editBookmark">Add</a></li> + <li><a href="/groups">Groups</a></li> + </ul> +</div> + +<div id="nuage"> + @for (tag : ctx.result.tags) { + <a href="/search?addTag=@tag.name" class="tag" style="font-size: 30px" title="@tag.count results">@tag.name</a> + } +</div> + +<div id="footer"> + <p> + <a shape="rect" href="http://www.chorem.org/projects/bow">bow</a> + <a shape="rect" href="http://www.chorem.org/projects/bow/files"></a> - + <a shape="rect" href="http://www.gnu.org/licenses/agpl.html">Licence AGPL</a> - + <span title="Copyright">©2010-2019</span> + <a shape="rect" href="http://www.codelutin.com">Code Lutin</a> - + <a shape="rect" href="http://www.chorem.org/projects/bow/issues">Rapport de bug</a> - + <a shape="rect" href="http://list.chorem.org/cgi-bin/mailman/listinfo/bow-users">Support utilisateur</a> - + <a href="/bow/home.action?request_locale=en_GB">English</a> + - + <a href="/bow/home.action?request_locale=fr_FR">Français</a> + </p> +</div> +</body> +</html> diff --git a/src/main/templates/views/listUsers.rocker.html b/src/main/templates/views/listUsers.rocker.html index 18497b5..8973dd0 100644 --- a/src/main/templates/views/listUsers.rocker.html +++ b/src/main/templates/views/listUsers.rocker.html @@ -3,7 +3,7 @@ @args (Collection<BowUser> users) -@views.main.template("List users", RockerContent.NONE, RockerContent.NONE) -> { +@views.layout.template("List users", RockerContent.NONE, RockerContent.NONE) -> { @for (user : users) { <li><a href="/editUser?id=@user.getId()">@?user.getLogin()</a></li> } diff --git a/src/main/templates/views/main.rocker.html b/src/main/templates/views/main.rocker.html deleted file mode 100644 index bbb75d0..0000000 --- a/src/main/templates/views/main.rocker.html +++ /dev/null @@ -1,12 +0,0 @@ -@args (String title, RockerContent extracss, RockerContent extrajs, RockerBody content) - -<html> -<head> - <title>@title</title> - @extracss -<body> -<div><a href="/">Home</a></div> -@content -@extrajs -</body> -</html> \ No newline at end of file diff --git a/src/main/templates/views/search.rocker.html b/src/main/templates/views/search.rocker.html new file mode 100644 index 0000000..98da7db --- /dev/null +++ b/src/main/templates/views/search.rocker.html @@ -0,0 +1,72 @@ +@import com.chorem.bow.BowContext + +@args(BowContext ctx) + +@views.layout.template("search", RockerContent.NONE, RockerContent.NONE) -> { +<div class="global-action"> + <h2> + @ctx.result.offset-@(ctx.result.offset + ctx.result.count)/@ctx.result.total<a href="/search?first=@(ctx.result.offset + ctx.result.count">>></a> + </h2> + + <a id="deleteSearchResultsButton" title="delete all" onclick="deleteConfirmation('/deleteSearchResults', @ctx.result.total); return(false);"></a> + <form id="home" action="/search" method="get"><label for="order">Trier par</label> + <select name="order" id="order"> + <option value="ascVisit" @("ascVisit".equals(ctx.search.order)?selected:"")>ascVisit</option> + <option value="descVisit" @("descVisit".equals(ctx.search.order)?selected:"")>descVisit</option> + <option value="ascDate" @("ascDate".equals(ctx.search.order)?selected:"")>ascDate</option> + <option value="descDate" @("descDate".equals(ctx.search.order)?selected:"")>descDate</option> + </select> + + <input type="submit" value="apply" name="submit"/> + </form> +</div> + +<div class="result"> + @for (item : ctx.result.items) { + <div class="bookmark"> + <div class="bookmarkhead"> + <span class="left"><img src="img/ptit-livre.jpg"/></span> + <span class="left date">05/04/09</span> + <span class="spacemax"></span> + <span class="right"> + <a href="/screenshotBookmark?id=a204ce69-c380-4a0b-b00d-b146ae13abf0" class="screenshotLink"></a> + </span> + <span class="right"> + <a href="/authenticationEdit?bookmarkId=a204ce69-c380-4a0b-b00d-b146ae13abf0" class="edit-authentication"></a> + </span> + <span class="right"> + <a href="/bow/editBookmark?id=a204ce69-c380-4a0b-b00d-b146ae13abf0" class="edit"></a> + </span> + <span class="right"> + <a href="/deleteBookmark?bookmarkId=a204ce69-c380-4a0b-b00d-b146ae13abf0" class="supprim" onclick="return deleteConfirmation('/deleteBookmark?bookmarkId=@item.id', '@item.uri');"></a> + </span> + </div> + + <div class="bookmarkcontenu"> + <div class="screenshot"> + <a href="/alias/@item.id" title="@item.uri" target="_blank"> + <img src="data:image/png;base64,U29ycnk6Cg=="/> + </a> + </div> + <div class="visit">@item.visit</div> + <div class="description"> + <p> + <a href="/alias/@item.id">@item.uri</a> + </p> + <h3>Description :</h3> + <div class="markdown"> + @item.description + </div> + <p class="tags"> + <strong>Tags :</strong> + @for (tag : item.tags) { + <a href="/bow/home.action?addTag=@tag" class="tag">@tag</a> + } + </p> + </div> + </div> + </div> + } +</div> + +} \ No newline at end of file diff --git a/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java b/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java new file mode 100644 index 0000000..d3907a8 --- /dev/null +++ b/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java @@ -0,0 +1,126 @@ +package com.chorem.bow.rest; + +import com.chorem.bow.BowApp; +import com.chorem.bow.model.AuthenticationInfo; +import com.chorem.bow.model.Bookmark; +import com.chorem.bow.model.BowUser; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jooby.JoobyTest; +import io.jooby.StatusCode; +import io.jooby.json.JacksonModule; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@JoobyTest(BowApp.class) +public class BookmarkResourcesTest { + + public String serverPath; + + OkHttpClient client = new OkHttpClient(); + ObjectMapper jsonMapper = JacksonModule.create() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + + @Test + public void unitTest() throws Throwable { + Bookmark bookmark = new Bookmark(); + bookmark.setUri("http://www.codelutin.com"); + bookmark.setDescription("Site web de codelutin"); + bookmark.setTagsAsString("search postgresql doc facet db"); + + System.out.println("Creation bookmark"); + // CREATION + String userAsJson = jsonMapper.writeValueAsString(bookmark); + RequestBody body = RequestBody.create(userAsJson, MediaType.get("application/json")); + Request request = new Request.Builder() + .url(serverPath + "bookmarks") + .post(body) + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.OK.value(), response.code()); + bookmark = jsonMapper.readValue(response.body().string(), Bookmark.class); + } + + System.out.println("Update bookmarks"); + // UPDATE + AuthenticationInfo authenticationInfo = new AuthenticationInfo(); + authenticationInfo.setLogin("bpoussin"); + bookmark.setAuthenticationInfo(authenticationInfo); + bookmark.setPrivateAliasAsString("cl lutins"); + userAsJson = jsonMapper.writeValueAsString(bookmark); + body = RequestBody.create(userAsJson, MediaType.get("application/json")); + request = new Request.Builder() + .url(serverPath + "bookmarks/" + bookmark.getId()) + .put(body) + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.OK.value(), response.code()); + bookmark = jsonMapper.readValue(response.body().string(), Bookmark.class); + } + + + System.out.println("search bookmark"); + // GET + request = new Request.Builder() + .url(serverPath + "bookmarks?tag=postgresql") + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.OK.value(), response.code()); + System.out.println(response.body().string()); + } + + System.out.println("search all bookmark"); + // GET + request = new Request.Builder() + .url(serverPath + "bookmarks?fetch=2") + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.OK.value(), response.code()); + System.out.println(response.body().string()); + } + + System.out.println("Delete bookmark"); + // DELETE + request = new Request.Builder() + .url(serverPath + "bookmarks/" + bookmark.getId()) + .delete() + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.OK.value(), response.code()); + bookmark = jsonMapper.readValue(response.body().string(), Bookmark.class); + } + + System.out.println("Get bookmark"); + // GET + request = new Request.Builder() + .url(serverPath + "bookmarks/" + bookmark.getId()) + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + Assertions.assertEquals(StatusCode.NOT_FOUND.value(), response.code()); + } + + } + + +} -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.