branch bow-v2 updated (8d2105b -> a10748b)
This is an automated email from the git hooks/post-receive script. New change to branch bow-v2 in repository bow. See https://gitlab.nuiton.org/chorem/bow.git from 8d2105b ajout du README.md new a10748b script de migration fonctionnel The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit a10748b308af47c0d4bc3e301cce8eca8860744f Author: Benjamin <poussin@codelutin.com> Date: Wed Jul 31 00:00:36 2019 +0200 script de migration fonctionnel Summary of changes: 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 ++ ...sourcesTest.java => BookmarkResourcesTest.java} | 67 +- 18 files changed, 1267 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/chorem/bow/BowContext.java create mode 100644 src/main/java/com/chorem/bow/SortOrder.java create mode 100644 src/main/java/com/chorem/bow/model/Token.java create mode 100644 src/main/resources/db/migration/V2__migration_data.sql create mode 100644 src/main/resources/static/css/global.css create mode 100644 src/main/resources/static/img/favicon.png create mode 100644 src/main/templates/views/layout.rocker.html delete mode 100644 src/main/templates/views/main.rocker.html create mode 100644 src/main/templates/views/search.rocker.html copy src/test/java/com/chorem/bow/rest/{BowUserResourcesTest.java => BookmarkResourcesTest.java} (57%) -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
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>.
participants (1)
-
chorem.org scm