branch bow-v2-go created (now e16d8f0)
This is an automated email from the git hooks/post-receive script. New change to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git at e16d8f0 ajout de la migration de base automatique This branch includes the following new commits: new 45be0b1 debut d'ecriture des services rest en go new e16d8f0 ajout de la migration de base automatique The 2 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 e16d8f0919c72d202e3ac7e5452feb4895d811b6 Author: Benjamin <poussin@codelutin.com> Date: Mon Apr 6 01:33:37 2020 +0200 ajout de la migration de base automatique commit 45be0b1ef8176639e0681ec87e8acd5e20978d67 Author: Benjamin <poussin@codelutin.com> Date: Sun Apr 5 23:02:05 2020 +0200 debut d'ecriture des services rest en go -- 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-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit 45be0b1ef8176639e0681ec87e8acd5e20978d67 Author: Benjamin <poussin@codelutin.com> Date: Sun Apr 5 23:02:05 2020 +0200 debut d'ecriture des services rest en go --- .gitlab-ci.yml | 10 +- Dockerfile | 15 + README.md | 4 - TODO.md | 130 ---- cmd/bow/main.go | 32 + conf/application.conf | 15 - conf/logback.xml | 42 -- doc/implementation.md | 26 + env-dev | 1 + go.mod | 11 + go.sum | 124 ++++ http/actionResource.go | 8 + http/bookmarkResource.go | 76 ++ http/router.go | 46 ++ http/systemResource.go | 12 + http/userResource.go | 248 +++++++ log/access.log | 0 .../001_init_schema.sql | 28 +- .../002_migration_data.sql | 0 model/authenticationInfo.go | 20 + model/bookmark.go | 26 + model/user.go | 50 ++ pom.xml | 130 ---- repository/bookmarkRepository.go | 98 +++ repository/database.go | 25 + repository/userRepository.go | 276 ++++++++ src/etc/stork.yml | 41 -- src/main/java/com/chorem/bow/BowApp.java | 85 --- src/main/java/com/chorem/bow/BowContext.java | 23 - src/main/java/com/chorem/bow/SortOrder.java | 5 - src/main/java/com/chorem/bow/model/Action.java | 7 - .../com/chorem/bow/model/AuthenticationInfo.java | 22 - src/main/java/com/chorem/bow/model/Bookmark.java | 47 -- src/main/java/com/chorem/bow/model/BowUser.java | 29 - src/main/java/com/chorem/bow/model/Token.java | 13 - .../bow/repositories/BookmarkRepository.java | 102 --- .../chorem/bow/repositories/BowUserRepository.java | 71 -- .../com/chorem/bow/rest/BookmarkResources.java | 94 --- .../java/com/chorem/bow/rest/BowUserResources.java | 81 --- src/main/java/com/chorem/spgeed/SpgeedModule.java | 60 -- .../java/com/chorem/spgeed/SqlSessionProvider.java | 19 - src/main/resources/static/css/global.css | 773 --------------------- src/main/resources/static/img/favicon.png | Bin 4267 -> 0 bytes src/main/templates/views/editUser.rocker.html | 25 - src/main/templates/views/index.rocker.html | 5 - src/main/templates/views/layout.rocker.html | 106 --- src/main/templates/views/listUsers.rocker.html | 10 - src/main/templates/views/search.rocker.html | 72 -- src/test/java/com/chorem/bow/BowAppTest.java | 19 - .../com/chorem/bow/rest/BookmarkResourcesTest.java | 126 ---- .../com/chorem/bow/rest/BowUserResourcesTest.java | 111 --- src/test/java/com/chorem/bow/rest/RestHelper.java | 26 - utils/password.go | 37 + utils/uuid.go | 27 + 54 files changed, 1189 insertions(+), 2300 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 190cd42..2aef5a2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,13 @@ stages: - build -build: +dockerise: + image: registry.nuiton.org/codelutin/dockerfiles:docker stage: build script: - - mvn clean package + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE . + - docker push $CI_REGISTRY_IMAGE + - docker logout $CI_REGISTRY tags: - - cloud + - docker diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..911878d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:alpine AS builder + +WORKDIR $GOPATH +COPY cmd cmd + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o /bin/bow cmd/bow/main.go + +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /bin/bow / + +# Expose port 8080 to the outside world +EXPOSE 8080 + +CMD ["/bow] diff --git a/README.md b/README.md deleted file mode 100644 index 09fd628..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -pour lancer le serveur: - - mvn jooby:run - diff --git a/TODO.md b/TODO.md deleted file mode 100644 index a2672c0..0000000 --- a/TODO.md +++ /dev/null @@ -1,130 +0,0 @@ -mettre dans le token JWT le user, pour ne plus a avoir a accéder à la base pour -chaque demande qu'il fait pour la récupération du user. - - -## Database - -- il faut que bowUser.emails soit unique sur tous les utilisateurs -- il faut que bookmark.publicAlias soit unique sur tous les utilisateurs - - -## Feature - -Ajouter un date de validité au token (surtout utile pour les pseudo user) - -Lorsqu'on met un @nom cela revient a utiliser un groupe privé automatiquement créer s'il n'existe pas - -On peut créer un #group. L'interface -d'admin permet d'ajouter des users du system (ce qui demande l'authorisation -à l'autre user pour être ajouté). Par configuration, l'interface peut ou non -permettre de lister les autres utilisateurs (installation en entreprise), mais -quoi qu'il arrive si un utilisateur est deja dans un des groupes il sera lister -pour aider la saisie. -Lorsque l'utilisateur accepter de rentrer dans un groupe, il peut aussi indiquer -que la personne qui invite peut le mettre maintenant dans autant de groupe quelle -veut sans que l'utilisateur soit a nouveau solicité. -On peut voir tous les groupes pour lequel on est invité mais pas encore accepter, -et tous les groupes auquels on appartient. -Il est possible de définir un group comme 'public' dans ce cas il peut-etre -recherché par tous les autres utilisateurs et ils peuvent faire la demande pour -y être accepté ou etre accepter automatiquement. - - -Group: -- UUID id -- String name (il ne peut y avoir qu'un seul group public avec le meme nom et donc a qui appartient l'url public, les autres utilisent leur id) -- String description -- {token: UUID, name: String, expirationDate: Date} token (permet des non utilisateurs de bow de voir les bookmarks) -- Set<BowUser> admin (au moins la personne qui la créé, il doit toujours y avoir au moins 1 admin) -- Set<BowUser> writer (les personnes qui ont le droit d'utiliser #group dans leur tags) -- Set<BowUser> reader (les personnes qui n'ont un acces qu'en lecture au bookmark, s'il utilise #group, - cela créera un group privé pour eux, se group ne pourra pas passer public - s'ils deviennent writer ou admin du group public, leur bookmark sont automatiquement enrolé dans le group public ) -- Set<BowUser> invite (l'invité n'a pas encore accepté) -- Set<BowUser> toModerate (admin n'a pas encore accepté cette utilisateurs) -- boolean advertised (si true alors ce groupe est listé dans la liste des groupes) -- boolean public (si true alors les bookmarks sont directement visible (lecture seule) sans authentification: https://bookmarks.cl/group/MonGroup) -- boolean moderated ((utile que si 'public' est vrai) false alors les utilisateurs qui demande a venir dans le groupe son accepté automatiquement) -- Enum memberDefaultVisiblity (ALL, MEMBER, ADMIN) -- Set<BowUser> restrictiveVisibility (la liste des membres qui veulent que seul l'admin puisse les voir dans la liste des membres) - -bookmarks.cl/group/MonGroup - -## En vrac - -### Tech -- single page -- back java (go?) (-> java) -- front html/js/css -- graphQL ou Rest ? (-> Rest) -- postgresl (embeded, mais configurable si souhait d'utiliser un postgresql externe) - -bow.cl/a/<alias> -bow.cl/avatar/<email> -bow.cl/rss/<uuid> (uuid d'une requete enregistrée) -bow.cl/openid -bow.cl/oauth -bow.cl/bookmarks/ - -### Fonctionnalité -- utilisation en tant que moteur de recherche dans les navigateurs -- interprétation de commandes programmables dans l'url (sauvegarde, recherche (tag, fulltext), redirection vers une url) -- enregistrement de bookmark (description + tag) -- sauvegarde d'une miniature de la page -- possibilité d'avoir un alias privé et/ou public (alias.cl) -- possibilité de généré un mot de passe unique pour un domain basé sur un master password (scriptlet, script shell) -- possibilité de partager avec d'autre (partage group privé, partage group public) -- génération flux RSS en fonction d'une recherche (par tag ou fulltext) -- comptabilise le nombre d'utilisation de chaque bookmark (click, alias, ...) -- possilité de créer des forms de login pour chaque bookmark (permet enregistrer par exemple le login) -- Bow est provider d'identité: openId, OAuth -- compatible OpenSearch -- import (Firefox) -- export - -### Idée -- bow doit garder l'historique des recherches -- lors des propositions proposer avec les url enregistrées dans Bow et de l'historique de recherche -- Bow doit permettre de faire server de sauvegarde de bookmark/password mozilla-sync -- mettre un interpreteur plus complexe des demandes passées dans l'url (un peu à la chatbot). -- sauvegarde du html de la page pour lecture même si le site disparait -- check régulier si le site existe encore -- check régulier si le contenu de la page à changé (génération d'un flux rss des pages modifiers)(l'utilisateur à la possibilité de choisir la fréquence, on bride la fréquence max pour les utilisateurs non payant: ex: pas plus de 3 bookmarks avec vérif journaliere; 5 avec verif hebdo, 20 avec verif mensuel, 100 avec verif annuel (fixé par config))(pour les payants conservation de chaque version) -- permettre d'avoir en critère de recherche les bookmarks avec auth, surveillé (avec contenu modifier il y a moins de N jours). L'utilisateur peut indiquer un chemin xpath (css path?) pour dire quelle partie l'interesse vraiment -- permettre d'avoir un critère de recherche dans les groups (bookmark ne m'appartenant pas mais que je peux voir) -- Si on va a un site via un alias et qu'il exist une form de login dans le bookmark, alors on est d'abord redirigé vers cette form et non pas vers le site -- permettre de partager un bookmark ou un resultat de recherche via: email, generation pdf, generation page web static, (tweeter, google+, facebook, ...) ? -- Permettre de faire une action sur tous les resultats d'une recherche: supprimer, ajouter un tag, exporter, .... Par exemple si on souhaite permettre a des personnes non inscrite de suivre notre veille. On crée un user 'shared'. On recherche tous les liens ajouté la semaine dernière, et on ajoute le tag '#shared' à tous les bookmarks de cette recherche et on demande l'url qui permette de rejouer cette recherche pour notre utilisateur shared -* Les bookmarklets peuvent etre utiliser sans le token permanent. Mais avec un cookie d'authentification. Ce cookie contient le WikittyToken courant. Ce cookie devient invalide si la personne clique sur ce déconnecté. Tant qu'elle ne la pas fait, le token est est valide et on authentifie l'utilisateur grace a lui. (a mettre en place en plus des tokenPermanent/tokenSession. -* email forwarder (permet d'avoir une adresse mail unique par site, on sait d'ou vient le spam :)): Pour chaque bookmark des emails sont créés de la form <user name>-<site domain>@bow.cl, qui redirige vers le vrai email de la personne en lui ajoutant +bow-<domain site>. Chaque utilisateur à la possibilité de créer de nouveau login comme il le veut en plus de ceux déjà créé (non supprimable). Chaque bookmark à donc une liste d'email de redirection -* Pour chaque login créé l'utilisateur peux lui ajouter une image (avatar) qui sera utilisée lorsque des sites tiers demandent l'avatar via (open avatar/pavatar/ il y a une norme d'avatar décentralisé) -* Ajouter une fonctionnalité pour qu'un utilisateur puisse supprimer son compte et tous ces bookmarks (double/triple verif avec email de confirmation et tout et tout) -* Stat: faire des statistiques sur quel navigateur on utilise le plus pour stocker des urls ou recherche des urls :), depuis quel lieu (ip), ... -* Faire un script qui permette de facilement genere une form HTML avec login/password que l'on voit dans bow mais qui une fois soumise nous redirige vers le vrai site (exemple: Credit Mutuel). Normalement ca devrait etre possible de mettre la forme en markdown et de genere le mot de passe via le BowAuthentication pour ce site avec un domain forcé. -* Nouveau processus de creation de compte. On demande la creation avec comme login un email. On créé un token sur le user et on envoie par mail un lien de registration avec ce token. Le compte est validé lorsque le lien est cliqué. Dans ce cas le token de registration est supprimé et le compte est alors actif. (restraint ton les droits sur un compte non actif ?) Lorsque la personne perd son mot de passe, on remet un token dans le field registration et on lui renvoie un email. Lorsqu'il c [...] -* Bow doit être un proviser d'authent: OpenId, OAuth, ... -* permettre de s'authentifier à Bow via: OpenId, OAuth, ... -* après avoir fait une requete, permettre d'enregistrer cette requete et de la nomer. Soit on conserve le resultat actuel en plus de la requete, soit on demande a ce quelle soit rejouer a chaque fois. Permettre ensuite de partager cette requetes avec le monde entier comme pour les alias publics. De cette facon on peut partager un ensemble de lien facilement. Cela creer en plus une URL RSS specifique (avec un UUID) que le l'on peut utiliser soit meme ou donner a d'autre pour qu'ils suivre [...] -* http://www.bortzmeyer.org/6596.html peut-etre utilise le canonical plutot que l'adresse de la page pour bookmarker ? mais pas sur, c'est surtout pertinant pour les moteurs de recherche. -* afficher les tags avec: http://cssglobe.com/lab/css3_tags/01.html -* The ability to bookmark by email (on envoie un email qui contient l'url, la description et les tags et ils sont ajoute au bookmarks) (un filtre sur l'adresse expéditeur est fait via regex (permet d'avoir un seul email, une liste d'email, ou un domaine) qui autorise l'ajout -* config en fonction du navigateur de l'utilisateur, s'il souhaite de l'ajax ou non. Valeur par defaut puis par navigateur utilise pour configurer. -* Packaging: jar executable (container de servlet inclus), script rc/systemd pour linux, package debian (postgresql embeded) - -- permettre de faire un peu comme http://www.scoop.it/ - -* [done] Permettre lors de l'ajout d'un lien d'indiquer facilement que l'ajout est pour un groupe en mettant le tag "@NomDuGroup". On defini les groupe via une page d'admin en indiquant les personnes faisant parti de ce groupe. (voir comment gere la suppression de ce tag ? voir comment faire pour que plusieurs personne alimente ce groupe en lien) -* [done] pouvoir creer des groups avec qui on partage des bookmarks -* [done] avoir des flux rss a partir d'une requete/tag -* [done] mettre en place la securite, pour eviter que certain utilise l'action removeBookmark pour supprimer des user ou des bookmark qui ne leur appartient pas. -* [done] Creation de pseudo-compte pour le partage de lien. BowSharedUser[description:String #pourquoi on a créé ce user] <- WikittyUser, WikittyAuthorisation. Ces users n'ont aucun menu, aucune zone de recherche, cela permet seulement de générer une url avec un token permanent que l'on peut partagé avec des personnes non inscrite a bow mais sans rendre public nos bookmarks. On a un menu pour creer ces users. Ces users créés sont personnel au user. Pour cela le login est prefixé du wikit [...] -* [done] permettre d'ajouter en critere de recherche une periode (deux dates) ceci en permettant beaucoup plus de chose dans le champs de recherche fulltext (ajout de tag, date, datefrom, dateto, ...)(ex:java tag:@cldev tag:-@clsys datefrom:20150601 dateto:20150630) (je pense qu'il suffit de faire des rechercher/remplacer pour mettre les bon nom de champs a la place de tag, date, datefrom, dateto de faire passer la requete dans WikittyQueryParser et de la jouer. -* [done] faire un bookmarklet pour ajouter une url: - * javascript:var bowtag=prompt("tag"); if (bowtag!==null){ location.href='https://bow.chorem.org/bow/addUrl?return=true& - link='+encodeURIComponent(location.href)+'& - description='+encodeURIComponent(getSelection().toString()||document.title||'')+'& - tagLine='+encodeURIComponent(tag)} - va sur bow, permet de corriger le bookmark (ajout de tag, description, alias, password, ...) (ou alors demande lorsqu'on est encore sur la page ? avec un prompt ?) et lorsqu'on sauve retourne a la page qui vient d'être bookmarkee si return = true. -* [done] faire en sorte que Bow soit provider d'identité: openId, OAuth, ... -* [done] pouvoir selectionner du texte sur la page a bookmarque qui serve de texte de bookmark -* [done] ameliorer le script add pour pouvoir choisir le type de bookmark (private, public, groups) diff --git a/cmd/bow/main.go b/cmd/bow/main.go new file mode 100644 index 0000000..1f6b15c --- /dev/null +++ b/cmd/bow/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "log" + "os" + + "gitlab.chorem.org/chorem/bookmarks/http" + "gitlab.chorem.org/chorem/bookmarks/repository" +) + +func main() { + databaseURL := os.Getenv("DATABASE_URL") + log.Println("Init database", databaseURL) + repository.Init(databaseURL) + + // u := model.BowUser{ID: utils.GenUUID(), Login: "bpoussin", Emails: []string{"benjamin@pouss.in"}, Password: "toto"} + + // err := repository.CreateUser(u) + // if err != nil { + // log.Fatalln(err) + // } + + // s, err := repository.UserJSON("benjamin@pouss.in") + // if err != nil { + // log.Fatalln(err) + // } + // log.Println("User: ", s) + + addr := ":8000" + log.Println("Start web server", addr) + http.Start(addr) +} diff --git a/conf/application.conf b/conf/application.conf deleted file mode 100644 index e365aaa..0000000 --- a/conf/application.conf +++ /dev/null @@ -1,15 +0,0 @@ -# add or override properties -# See https://github.com/typesafehub/config/blob/master/HOCON.md for more details - -db.url = "jdbc:postgresql://localhost:5444/bow" -db.user = "dbuser" -db.password = xxxxxxxx - -# hikari -hikari.autoCommit = false -hikari.maximumPoolSize = 20 - -# flyway -flyway.baselineOnMigrate=true -flyway.baselineVersion=0 -flyway.baselineDescription=Wikitty data diff --git a/conf/logback.xml b/conf/logback.xml deleted file mode 100644 index 934d01c..0000000 --- a/conf/logback.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<configuration scan="true" scanPeriod="15 seconds" debug="false"> - <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> - <encoder> - <pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern> - </encoder> - </appender> - - <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>log/bow.log</file> - <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> - <fileNamePattern>log/bow.%d{yyyy-MM-dd}.log</fileNamePattern> - <totalSizeCap>1mb</totalSizeCap> - <maxHistory>7</maxHistory> - </rollingPolicy> - - <encoder> - <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> - </encoder> - </appender> - - <appender name="ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>log/access.log</file> - <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> - <fileNamePattern>log/access.%d{yyyy-MM-dd}.log</fileNamePattern> - <totalSizeCap>1mb</totalSizeCap> - <maxHistory>7</maxHistory> - </rollingPolicy> - - <encoder> - <pattern>%msg%n</pattern> - </encoder> - </appender> - - <logger name="org.jooby.RequestLogger" additivity="false"> - <appender-ref ref="ACCESS" /> - </logger> - - <root level="INFO"> - <appender-ref ref="STDOUT" /> - </root> -</configuration> diff --git a/doc/implementation.md b/doc/implementation.md new file mode 100644 index 0000000..5a7bff9 --- /dev/null +++ b/doc/implementation.md @@ -0,0 +1,26 @@ +== Group + +Il est possible dans les tags de mettre un `@` devant un tag pour indiquer un +groupe. Si ce groupe existe il donnera accès aux personnes ayant le droit de +lecture dans ce groupe. + +Lorsqu'on recherche tous les tags `@toto` en tant qu'utilisateur, on a +forcément ses propres bookmarks qui ont le tag `@toto`, mais aussi tout ceux +des groupes auquels ont appartient et auquel le owner du bookmark appartient +aussi en tant que writer ou admin (mais pas reader). + +Un groupe a des admins qui peuvent ajouter (retirer?) d'autres utilisateurs dans le +groupe. Par défaut le créateur est admin, mais il peut en ajouter d'autres. +Le groupe a des writers qui ont le droit d'ajouter ce même tags dans leurs bookmarks +et des readers qui peuvent voir les bookmarks qui ont ce tag. + +Des utilisateurs peuvent donc mettre le tag dans leurs bookmarks sans que +personne ne les voit jusqu'au jour on il sont ajouter à un groupe qui porte +ce nom. Il peut y avoir plusieurs groupes avec le même nom, tout dépendra +de qui est dans le groupe. + +les admins d'un groupe peuvent générer un token d'authentification pour un +groupe, ce qui permettra de diffuser une url avec ce token et ainsi permet +à n'importe qui de voir ces bookmarks + +Un groupe est supprimé lorsqu'il n'y a plus d'utilisateur (admin, writer, reader) diff --git a/env-dev b/env-dev new file mode 100644 index 0000000..5c28813 --- /dev/null +++ b/env-dev @@ -0,0 +1 @@ +export DATABASE_URL="postgres://dbuser:xxxxxxxx@localhost:5432/bow" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..09c369a --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module gitlab.chorem.org/chorem/bookmarks + +go 1.14 + +require ( + github.com/gorilla/mux v1.7.4 + github.com/jackc/pgtype v1.3.0 + github.com/jackc/pgx/v4 v4.6.0 + golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eea20df --- /dev/null +++ b/go.sum @@ -0,0 +1,124 @@ +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0= +github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= +github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/http/actionResource.go b/http/actionResource.go new file mode 100644 index 0000000..e8f2747 --- /dev/null +++ b/http/actionResource.go @@ -0,0 +1,8 @@ +package http + +import ( + "net/http" +) + +func doActions(w http.ResponseWriter, r *http.Request) { +} diff --git a/http/bookmarkResource.go b/http/bookmarkResource.go new file mode 100644 index 0000000..663036b --- /dev/null +++ b/http/bookmarkResource.go @@ -0,0 +1,76 @@ +package http + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "gitlab.chorem.org/chorem/bookmarks/model" + "gitlab.chorem.org/chorem/bookmarks/repository" +) + +func addBookmark(w http.ResponseWriter, r *http.Request) { + var bookmark model.Bookmark + err := json.NewDecoder(r.Body).Decode(&bookmark) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + id, err := repository.CreateBookmark(bookmark) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + json.NewEncoder(w).Encode(map[string]string{"id": id}) +} + +func deleteBookmark(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + log.Println("Delete bookmark", id) + err := repository.DeleteBookmark(id) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + +} + +/* +updateBookmark save bookmark withour AuthenticationInfo +*/ +func updateBookmark(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + var bookmark model.Bookmark + err := json.NewDecoder(r.Body).Decode(&bookmark) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + bookmark.ID = id + err = repository.UpdateBookmark(bookmark) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateBookmarkAuthenticationInfo(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + var auth model.AuthenticationInfo + err := json.NewDecoder(r.Body).Decode(&auth) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + err = repository.UpdateBookmarkAuthenticationInfo(id, auth) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} diff --git a/http/router.go b/http/router.go new file mode 100644 index 0000000..5fb2ca5 --- /dev/null +++ b/http/router.go @@ -0,0 +1,46 @@ +package http + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/mux" +) + +/* +Start web server +*/ +func Start(addr string) { + router := mux.NewRouter() + + s := router.PathPrefix("/api/v1").Subrouter() + s.HandleFunc("/system/liveness", isAlive).Methods("GET") + s.HandleFunc("/users", createUser).Methods("POST") + s.HandleFunc("/users/{id}", deleteUser).Methods("DELETE") + s.HandleFunc("/users/{id}/password", updateUserPassword).Methods("PUT") + s.HandleFunc("/users/{id}/token", addUserToken).Methods("POST") + s.HandleFunc("/users/{id}/unconfirmedemails", addUserUnconfirmedEmail).Methods("POST") + s.HandleFunc("/users/{id}/unconfirmedemails/{token}", confirmUserEmail).Methods("GET") + s.HandleFunc("/users/{id}/authenticationinfo", updateUserAuthenticationInfo).Methods("PUT") + s.HandleFunc("/users/{id}/actions", updateUserActions).Methods("PUT") + s.HandleFunc("/users/{id}/autoscreenshot", updateUserAutoScreenshot).Methods("PUT") + s.HandleFunc("/users/{id}/autofavicon", updateUserAutoFavicon).Methods("PUT") + s.HandleFunc("/users/{id}/maxtagincloud", updateUserMaxTagInCloud).Methods("PUT") + s.HandleFunc("/users/{id}/maxresult", updateUserMaxResult).Methods("PUT") + s.HandleFunc("/bookmarks", addBookmark).Methods("POST") + s.HandleFunc("/bookmarks/{id}", deleteBookmark).Methods("DELETE") + s.HandleFunc("/bookmarks/{id}", updateBookmark).Methods("PUT") + s.HandleFunc("/bookmarks/{id}/authenticationinfo", updateBookmarkAuthenticationInfo).Methods("PUT") + s.HandleFunc("/actions", doActions).Methods("POST") + + srv := &http.Server{ + Handler: router, + Addr: addr, + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) +} diff --git a/http/systemResource.go b/http/systemResource.go new file mode 100644 index 0000000..c8afefd --- /dev/null +++ b/http/systemResource.go @@ -0,0 +1,12 @@ +package http + +import ( + "encoding/json" + "log" + "net/http" +) + +func isAlive(w http.ResponseWriter, r *http.Request) { + log.Println("http liveness") + json.NewEncoder(w).Encode(map[string]bool{"ok": true}) +} diff --git a/http/userResource.go b/http/userResource.go new file mode 100644 index 0000000..9b2618b --- /dev/null +++ b/http/userResource.go @@ -0,0 +1,248 @@ +package http + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "gitlab.chorem.org/chorem/bookmarks/model" + "gitlab.chorem.org/chorem/bookmarks/repository" +) + +/* +createUser +body: {"login": "toto", "password": "xxxx"} +return: {"id": "[uuid]"} +*/ +func createUser(w http.ResponseWriter, r *http.Request) { + var data map[string]string + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("createUser", data["login"]) + + id, err := repository.CreateUser(data["login"], data["password"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + json.NewEncoder(w).Encode(map[string]string{"id": id}) +} + +func deleteUser(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + log.Println("deleteUser", id) + + err := repository.DeleteUser(id) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +/* +createUser +body: {"password": "xxxx", "oldPassword": "yyyy"} +*/ +func updateUserPassword(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + log.Println("updateUserPassword", id) + + var data map[string]string + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + err = repository.UpdateUserPassword(id, data["password"], data["oldPassword"], false) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +/* +addUserToken +body: {"name": "for application toto", "expiration": 1586081695000} +return: {"token": "uuid"} +*/ +func addUserToken(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data model.Token + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("addUserToken", id, data.Name, data.Expiration) + + token, err := repository.AddUserToken(id, data.Name, data.Expiration) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + json.NewEncoder(w).Encode(map[string]string{"token": token}) +} + +/* +addUserUnconfirmedEmail +body: {"name": "for application toto", "expiration": 1586081695000} +return: {"token": "uuid"} +*/ +func addUserUnconfirmedEmail(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data model.UnconfirmedEmails + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("addUserUnconfirmedEmail", id) + + token, err := repository.AddUserUnconfirmedEmail(id, data.Email) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + json.NewEncoder(w).Encode(map[string]string{"token": token}) + + // TODO send email confirmation with token link +} + +func updateUserAuthenticationInfo(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + var auth model.AuthenticationInfo + err := json.NewDecoder(r.Body).Decode(&auth) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserAuthenticationInfo", id, auth) + + err = repository.UpdateUserAuthenticationInfo(id, auth) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateUserActions(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + var actions []model.Action + err := json.NewDecoder(r.Body).Decode(&actions) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserActions", id, actions) + + err = repository.UpdateUserActions(id, actions) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateUserAutoScreenshot(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data map[string]bool + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserAutoScreenshot", id, data) + + err = repository.UpdateUserAutoScreenshot(id, data["autoscreenshot"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateUserAutoFavicon(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data map[string]bool + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserAutoFavicon", id, data) + + err = repository.UpdateUserAutoFavicon(id, data["autofavicon"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateUserMaxTagInCloud(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data map[string]int8 + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserMaxTagInCloud", id, data) + + err = repository.UpdateUserMaxTagInCloud(id, data["maxtagincloud"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func updateUserMaxResult(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + var data map[string]int8 + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + log.Println("updateUserMaxResult", id, data) + + err = repository.UpdateUserMaxResult(id, data["maxresult"]) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } +} + +func confirmUserEmail(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + token := mux.Vars(r)["token"] + + err := repository.ConfirmUserEmail(id, token) + if err != nil { + http.Error(w, fmt.Sprintf("%s", err), 400) + return + } + + // TODO: retourner une page indiquant que l'email est bien validé +} diff --git a/log/access.log b/log/access.log deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/db/migration/V1__init_schema.sql b/migrate/001_init_schema.sql similarity index 81% rename from src/main/resources/db/migration/V1__init_schema.sql rename to migrate/001_init_schema.sql index fd38d1b..c1ec3d8 100644 --- a/src/main/resources/db/migration/V1__init_schema.sql +++ b/migrate/001_init_schema.sql @@ -43,15 +43,15 @@ create table bowUser ( updateDate timestamp DEFAULT current_timestamp, login Text, password Text, - tokens jsonb, -- Token[], + tokens jsonb, -- [{name: sring, token: string, expire: date}] emails TEXT[], - unconfirmedEmails jsonb, -- en cle l'email, en valeur l'uuid qui permet la validation + unconfirmedEmails jsonb, -- [{email: string, token: string, creationDate: date}] authenticationInfo jsonb, -- AuthenticationInfo, autoScreenshot boolean, autoFavicon boolean, maxTagInCloud smallint, maxResult smallint, - actions jsonb -- Action[] + actions jsonb -- [{"action": string, "prefix": string, "suggest": string}] ); CREATE UNIQUE INDEX bowUser_login_idx ON BowUser (login); @@ -59,6 +59,24 @@ CREATE INDEX bowUser_token_idx ON BowUser USING gin (tokens); CREATE UNIQUE INDEX bowUser_emails_idx ON BowUser (unnest(emails)); CREATE INDEX bowUser_unconfirmedEmails_idx ON BowUser USING gin (unconfirmedEmails); +create table group ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + creationDate timestamp DEFAULT current_timestamp, + updateDate timestamp DEFAULT current_timestamp, + name Text, + password Text, + tokens jsonb, -- [{name: sring, token: string, expiration: date}] + admin UUID[], + writer UUID[], + reader UUID[] +} + +CREATE INDEX group_name_idx ON group (name); +CREATE INDEX group_token_idx ON group USING gin (tokens); +CREATE INDEX group_admin_idx ON group (unnest(admin)); +CREATE INDEX group_writer_idx ON group (unnest(writer)); +CREATE INDEX group_reader_idx ON group (unnest(reader)); + create table bookmark ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner UUID REFERENCES bowUser(id) ON DELETE CASCADE ON UPDATE CASCADE, @@ -82,7 +100,7 @@ CREATE INDEX bookmark_tags_idx ON bookmark USING gin (tags); -- index pour la r CREATE INDEX bookmark_tags_like_idx ON bookmark USING gin (text(tags) gin_trgm_ops); -- index pour la recherche via ilike "%toto%" CREATE INDEX bookmark_description_idx ON bookmark USING GIN (to_tsvector(lang, description)); CREATE INDEX bookmark_privateAlias_idx ON bookmark USING gin (privateAlias); -CREATE INDEX bookmark_publicAlias_idx ON bookmark USING gin (publicAlias); +CREATE UNIQUE INDEX bookmark_publicAlias_idx ON bookmark USING gin (publicAlias); -- penser au nettoyage de cette table, si plus aucun bookmark ne reference cette uri create table pageHistory ( @@ -128,9 +146,11 @@ END; $$ language 'plpgsql'; CREATE TRIGGER update_BowUser_createDate BEFORE INSERT ON BowUser FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); +CREATE TRIGGER update_group_createDate BEFORE INSERT ON group FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_Bookmark_createDate BEFORE INSERT ON Bookmark FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_PageHistory_createDate BEFORE INSERT ON pageHistory FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_ActionHistory_createDate BEFORE INSERT ON actionHistory FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_BowUser_updateDate BEFORE INSERT OR UPDATE ON BowUser FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); +CREATE TRIGGER update_group_updateDate BEFORE INSERT OR UPDATE ON group FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); CREATE TRIGGER update_Bookmark_updateDate BEFORE INSERT OR UPDATE ON Bookmark FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); diff --git a/src/main/resources/db/migration/V2__migration_data.sql b/migrate/002_migration_data.sql similarity index 100% rename from src/main/resources/db/migration/V2__migration_data.sql rename to migrate/002_migration_data.sql diff --git a/model/authenticationInfo.go b/model/authenticationInfo.go new file mode 100644 index 0000000..98e0797 --- /dev/null +++ b/model/authenticationInfo.go @@ -0,0 +1,20 @@ +package model + +/* +AuthenticationInfo contient les informations pour +s'authentifier sur une site + + DomainComponent: le nombre de composant du domain a prendre (default 2, ex: codelutin.com) + Salt: chaine ajouter en prefix du master password avant le hash + Suffix: chaine ajouter en suffix du hash final genere (le password genere peut donc etre plus long que maxLength) +*/ +type AuthenticationInfo struct { + Description string `json:"description,omitempty"` + Login string `json:"login,omitempty"` + DomainComponent byte `json:"domaincomponent,omitempty"` + MaxLength byte `json:"maxlength,omitempty"` + AllowedChar string `json:"allowedchar,omitempty"` + DisallowedChar string `json:"disallowedchar,omitempty"` + Salt string `json:"salt,omitempty"` + Suffix string `json:"suffix,omitempty"` +} diff --git a/model/bookmark.go b/model/bookmark.go new file mode 100644 index 0000000..3adaa7e --- /dev/null +++ b/model/bookmark.go @@ -0,0 +1,26 @@ +package model + +import ( + "time" +) + +/* +Bookmark lien a sauvegarder +*/ +type Bookmark struct { + ID string `json:"id,omitempty"` + Owner string `json:"owner,omitempty"` + URI string `json:"uri,omitempty"` + Description string `json:"description,omitempty"` + Tags []string `json:"tags,omitempty"` + CreationDate time.Time `json:"creationdate,omitempty"` + UpdateDate time.Time `json:"updatedate,omitempty"` + ImportDate time.Time `json:"importdate,omitempty"` + PrivateAlias []string `json:"privatealias,omitempty"` + PublicAlias []string `json:"publicalias,omitempty"` + AuthenticationInfo AuthenticationInfo `json:"authenticationinfo,omitempty"` + Favicon []byte `json:"favicon,omitempty"` + Screenshot []byte `json:"screenshot,omitempty"` + Visit int `json:"visit,omitempty"` + Lang string `json:"lang,omitempty"` +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..8eb2de9 --- /dev/null +++ b/model/user.go @@ -0,0 +1,50 @@ +package model + +import ( + "time" +) + +/* +BowUser un utilisateur de bow avec ses emails et ses preferences + + Tokens: en cle le token, en valeur l'app qui l'utilise (pour quelle raison se token existe + Emails: en cle l'email, en valeur une chaine random (uuid) qui permet la validation +*/ +type BowUser struct { + ID string `json:"id,omitempty"` + CreationDate time.Time `json:"creationdate,omitempty"` + UpdateDate time.Time `json:"updatedate,omitempty"` + Login string `json:"login,omitempty"` + Password string `json:"password,omitempty"` + Tokens []Token `json:"tokens,omitempty"` + Emails []string `json:"emails,omitempty"` + UnconfirmedEmails []UnconfirmedEmails `json:"unconfirmedemails,omitempty"` + AuthenticationInfo AuthenticationInfo `json:"authenticationinfo,omitempty"` + AutoScreenshot bool `json:"autoscreenshot"` + AutoFavicon bool `json:"autofavicon"` + MaxTagInCloud int8 `json:"maxtagincloud,omitempty"` + MaxResult int8 `json:"maxresult,omitempty"` + Actions []Action `json:"actions,omitempty"` +} + +type Token struct { + Name string + Token string + Expiration time.Time +} + +type UnconfirmedEmails struct { + Email string + Token string + CreationDate time.Time +} + +/* +Action une action est un raccourci (prefix) utilise dans la +bare d'adresse +*/ +type Action struct { + Prefix string `json:"prefix,omitempty"` + Action string `json:"action,omitempty"` + Suggest string `json:"suggest,omitempty"` +} diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 4f7b29e..0000000 --- a/pom.xml +++ /dev/null @@ -1,130 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>io.jooby</groupId> - <artifactId>jooby-project</artifactId> - <version>2.6.1</version> - </parent> - - <artifactId>bow</artifactId> - <groupId>com.chorem</groupId> - <version>2.0-SNAPSHOT</version> - - <name>bow</name> - <description>Bow - bookmarks On the Web</description> - - <properties> - <jooby.version>2.6.1</jooby.version> - <rocker.version>1.2.3</rocker.version> - <spgeed.version>1.0.10-SNAPSHOT</spgeed.version> - <lombok.version>1.18.12</lombok.version> - - <!-- Startup class --> - <application.class>com.chorem.bow.BowApp</application.class> - </properties> - - <dependencies> - <!-- Server --> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-netty</artifactId> - </dependency> - - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-jackson</artifactId> - </dependency> - - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-rocker</artifactId> - </dependency> - - <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>${lombok.version}</version> - <scope>provided</scope> - </dependency> - - <!-- persistence --> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-hikari</artifactId> - </dependency> - - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-flyway</artifactId> - </dependency> - - <dependency> - <groupId>org.nuiton</groupId> - <artifactId>spgeed</artifactId> - <version>${spgeed.version}</version> - </dependency> - - <!-- logging --> - <dependency> - <groupId>ch.qos.logback</groupId> - <artifactId>logback-classic</artifactId> - </dependency> - - <!-- Tests --> - <dependency> - <groupId>io.jooby</groupId> - <artifactId>jooby-test</artifactId> - </dependency> - - <dependency> - <groupId>com.squareup.okhttp3</groupId> - <artifactId>okhttp</artifactId> - <version>4.0.1</version> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <compilerArgs> - <arg>-parameters</arg> - </compilerArgs> - </configuration> - </plugin> - <plugin> - <groupId>com.fizzed</groupId> - <artifactId>rocker-maven-plugin</artifactId> - <version>${rocker.version}</version> - <configuration> - <templateDirectory>src/main/templates</templateDirectory> - </configuration> - <executions> - <execution> - <id>generate-rocker-templates</id> - <phase>generate-sources</phase> - <goals> - <goal>generate</goal> - </goals> - </execution> - </executions> - </plugin> - <!-- Build fat jar --> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> - </plugin> - <plugin> - <groupId>io.jooby</groupId> - <artifactId>jooby-maven-plugin</artifactId> - </plugin> - </plugins> - </build> - -</project> diff --git a/repository/bookmarkRepository.go b/repository/bookmarkRepository.go new file mode 100644 index 0000000..a84dee1 --- /dev/null +++ b/repository/bookmarkRepository.go @@ -0,0 +1,98 @@ +package repository + +import ( + "context" + + "encoding/json" + + "github.com/jackc/pgtype" + "gitlab.chorem.org/chorem/bookmarks/model" +) + +/* +BookmarkJSON retourne le bookmark au format json +*/ +func BookmarkJSON(id string) (string, error) { + var pgjson pgtype.JSON + row := db.QueryRow(context.Background(), `WITH __all AS (select * from bookmark where id=$1) SELECT json_agg(__all.*) as j FROM __all`, id) + err := row.Scan(&pgjson) + + if err != nil { + return "", err + } + + var result string + err = pgjson.AssignTo(&result) + + return result, err +} + +/* +CreateBookmark creation d'un nouveau bookmark +return: id, err +*/ +func CreateBookmark(bookmark model.Bookmark) (string, error) { + bookmarkAsJSON, err := json.Marshal(bookmark) + if err != nil { + return "", err + } + + var pguuid pgtype.UUID + row := db.QueryRow(context.Background(), `INSERT INTO bookmark AS t SELECT * FROM json_populate_record(NULL::bookmark, $1::json) RETURNING t.id`, string(bookmarkAsJSON)) + err = row.Scan(&pguuid) + + if err != nil { + return "", err + } + + var result string + err = pguuid.AssignTo(&result) + + return result, err +} + +/* +UpdateBookmark creation d'un nouveau bookmark +*/ +func UpdateBookmark(bookmark model.Bookmark) error { + bookmarkAsJSON, err := json.Marshal(bookmark) + if err != nil { + return err + } + + _, err = db.Exec(context.Background(), ` + UPDATE bookmark AS t + SET (uri, description, privateAlias, publicAlias, lang) = + (SELECT uri, description, privateAlias, publicAlias, lang + FROM json_populate_record(NULL::bookmark, $2::json)) + WHERE id=$1`, bookmark.ID, string(bookmarkAsJSON)) + + return err + +} + +/* +UpdateBookmarkAuthenticationInfo creation d'un nouveau bookmark +*/ +func UpdateBookmarkAuthenticationInfo(id string, auth model.AuthenticationInfo) error { + authAsJSON, err := json.Marshal(auth) + if err != nil { + return err + } + + _, err = db.Exec(context.Background(), ` + UPDATE bookmark AS t + SET (authenticationinfo) = (SELECT * FROM json_populate_record(NULL::authenticationinfo, $2::json)) + WHERE id=$1`, id, string(authAsJSON)) + + return err +} + +/* +DeleteBookmark suppression d'un nouveau bookmark +*/ +func DeleteBookmark(id string) error { + _, err := db.Exec(context.Background(), `DELETE FROM bookmark WHERE id=$1`, id) + + return err +} diff --git a/repository/database.go b/repository/database.go new file mode 100644 index 0000000..b066216 --- /dev/null +++ b/repository/database.go @@ -0,0 +1,25 @@ +package repository + +import ( + "context" + "log" + + "github.com/jackc/pgx/v4/pgxpool" +) + +var db *pgxpool.Pool + +/* +Init initialise la connexion a la base en utilisant +*/ +func Init(databaseURL string) { + poolConfig, err := pgxpool.ParseConfig(databaseURL) + if err != nil { + log.Fatalln("Unable to parse DATABASE_URL", "error", err) + } + + db, err = pgxpool.ConnectConfig(context.Background(), poolConfig) + if err != nil { + log.Fatalln("Unable to create connection pool", databaseURL, "error", err) + } +} diff --git a/repository/userRepository.go b/repository/userRepository.go new file mode 100644 index 0000000..9f29796 --- /dev/null +++ b/repository/userRepository.go @@ -0,0 +1,276 @@ +package repository + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "time" + + "github.com/jackc/pgtype" + "gitlab.chorem.org/chorem/bookmarks/model" + "gitlab.chorem.org/chorem/bookmarks/utils" +) + +/* +UserJSON retourne l'utilisateur au format json +*/ +func UserJSON(loginOrEmail string) (string, error) { + var pgjson pgtype.JSON + row := db.QueryRow(context.Background(), `WITH __all AS (select * from bowuser where login=$1 or $1 = ANY(emails)) SELECT json_agg(__all.*) as j FROM __all`, loginOrEmail) + err := row.Scan(&pgjson) + if err != nil { + return "", err + } + + var result string + err = pgjson.AssignTo(&result) + + return result, err +} + +/* +UserJSON retourne l'utilisateur au format json +*/ +func checkPassword(idOrLoginOrEmail string, password string) bool { + var hash string + row := db.QueryRow(context.Background(), `select password from bowuser where id=$1 or login=$1 or $1 = ANY(emails)`, idOrLoginOrEmail) + err := row.Scan(&hash) + if err != nil { + return false + } + + return utils.CheckPassword(password, hash) +} + +/* +CreateUser retourne l'utilisateur au format json +*/ +func CreateUser(login string, password string) (string, error) { + hashPassword, err := utils.HashPassword(password) + if err != nil { + return "", err + } + + uuid, err := utils.GenUUID() + if err != nil { + return "", err + } + + user := model.BowUser{ + ID: uuid, Login: login, Password: hashPassword, MaxTagInCloud: 20, MaxResult: 20, AutoFavicon: false, AutoScreenshot: false, AuthenticationInfo: model.AuthenticationInfo{DomainComponent: 2, MaxLength: 15}} + userAsJSON, err := json.Marshal(user) + if err != nil { + return "", err + } + + log.Println("create user", string(userAsJSON)) + modif, err := db.Exec(context.Background(), `INSERT INTO bowUser AS t SELECT * FROM json_populate_record(NULL::bowUser, $1::json)`, string(userAsJSON)) + + if modif.RowsAffected() != 1 { + return "", errors.New("No user created") + } + + return user.ID, err +} + +/* +DeleteUser suppression d'un nouveau bookmark +*/ +func DeleteUser(id string) error { + modif, err := db.Exec(context.Background(), `DELETE FROM bowuser WHERE id=$1`, id) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +UpdateUserPassword update user password, if old password match, or if force is true +*/ +func UpdateUserPassword(id string, password string, oldPassword string, force bool) error { + if force || checkPassword(id, oldPassword) { + + hash, err := utils.HashPassword(password) + + if err == nil { + modif, err := db.Exec(context.Background(), `update bowuser SET password=$2 where id=$1;`, id, hash) + if err != nil { + return err + } + + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + } + + return err + + } + + return fmt.Errorf("Bad old password for user '%s'", id) +} + +/* +AddUserToken ajout un tocken d'authentification pour l'utilisateur +*/ +func AddUserToken(id string, name string, expiration time.Time) (string, error) { + token, err := utils.GenUUID() + if err != nil { + return "", err + } + + json, err := json.Marshal(model.Token{Name: name, Token: token, Expiration: expiration}) + if err != nil { + return token, err + } + + modif, err := db.Exec(context.Background(), `update bowuser SET tokens=coalesce(tokens, '[]'::jsonb) || $2::jsonb where id=$1;`, id, json) + if modif.RowsAffected() != 1 { + return "", fmt.Errorf("No user found for id '%s'", id) + } + + return token, err +} + +/* +AddUserUnconfirmedEmail ajout d'un email non confirme, retourne le token permettant la confirmation +*/ +func AddUserUnconfirmedEmail(id string, email string) (string, error) { + token, err := utils.GenUUID() + if err != nil { + return "", err + } + + json, err := json.Marshal(model.UnconfirmedEmails{Email: email, Token: token, CreationDate: time.Now()}) + if err != nil { + return token, err + } + + modif, err := db.Exec(context.Background(), `update bowuser SET unconfirmedemails=coalesce(unconfirmedemails, '[]'::jsonb) || $2::jsonb where id=$1;`, id, json) + if modif.RowsAffected() != 1 { + return "", fmt.Errorf("No user found for id '%s'", id) + } + + return token, err +} + +/* +UpdateUserAuthenticationInfo met a jour les infos d'authentification +*/ +func UpdateUserAuthenticationInfo(id string, auth model.AuthenticationInfo) error { + authAsJSON, err := json.Marshal(auth) + if err != nil { + return err + } + + modif, err := db.Exec(context.Background(), ` + UPDATE bowuser + SET (authenticationinfo) = (SELECT * FROM json_populate_record(NULL::authenticationinfo, $2::json)) + WHERE id=$1`, id, string(authAsJSON)) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + return err +} + +/* +UpdateUserActions met a jour les actions utilisateur +*/ +func UpdateUserActions(id string, actions []model.Action) error { + json, err := json.Marshal(actions) + if err != nil { + return err + } + + modif, err := db.Exec(context.Background(), `update bowuser SET actions=$2::jsonb where id=$1;`, id, json) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +UpdateUserAutoScreenshot met a jour le boolean d'auto screenshot de la page +*/ +func UpdateUserAutoScreenshot(id string, value bool) error { + modif, err := db.Exec(context.Background(), `update bowuser SET autoScreenshot=$2 where id=$1;`, id, value) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +UpdateUserAutoFavicon met a jour le boolean d'auto favicon de la page +*/ +func UpdateUserAutoFavicon(id string, value bool) error { + modif, err := db.Exec(context.Background(), `update bowuser SET autoFavicon=$2 where id=$1;`, id, value) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +UpdateUserMaxTagInCloud met a jour le nombre d'element du nuage de tag +*/ +func UpdateUserMaxTagInCloud(id string, value int8) error { + modif, err := db.Exec(context.Background(), `update bowuser SET maxTagInCloud=$2 where id=$1;`, id, value) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +UpdateUserMaxResult met a jour le nombre d'element affiché dans une page de resultat +*/ +func UpdateUserMaxResult(id string, value int8) error { + modif, err := db.Exec(context.Background(), `update bowuser SET maxResult=$2 where id=$1;`, id, value) + if modif.RowsAffected() != 1 { + return fmt.Errorf("No user found for id '%s'", id) + } + + return err +} + +/* +ConfirmUserEmail verif et confirme un email +*/ +func ConfirmUserEmail(id string, token string) error { + // le but est de simplement faire passer du statut non confirmer à confirmer un email + // si on le retrouve pour l'utilisateur et que le token est le bon. + // mais avec le schema de base choisi c'est un poil compliqué + // - on recheche les emails non confirmer du user et on les transformes le json en ligne + // - on ajoute la position de cette email (numero de ligne) + // - on ne garde que la ligne de l'email concerné + // - on update la ligne en faisant passer l'email dans les emails valides (ajout dans emails, suppression dans unconfirmedemails) + jsonPathToCheckToken := fmt.Sprintf(`$[*].token ? (@ == "%s")`, token) + modif, err := db.Exec(context.Background(), ` + with ue as (select jsonb_array_elements(unconfirmedemails) as json + from bowuser + where b.id=$1 and unconfirmedemails @? $3), + re as (select ROW_NUMBER () OVER () as index, * + from ue), + data as (select index, json->>'email' as email + from re + where json->>'token' = $2) + update bowuser as b + set + emails=array_append(b.emails, data.email), + unconfirmedemails=unconfirmedemails - (data.index::int - 1) + from data + where b.id=$1 and b.unconfirmedemails @? $3;`, id, token, jsonPathToCheckToken) + + if modif.RowsAffected() != 1 { + return errors.New("No user found to for this token") + } + return err +} diff --git a/src/etc/stork.yml b/src/etc/stork.yml deleted file mode 100644 index f2f2790..0000000 --- a/src/etc/stork.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Name of application (make sure it has no spaces) -name: "${project.artifactId}" - -# Display name of application (can have spaces) -display_name: "${project.name}" - -# Type of launcher (CONSOLE or DAEMON) -type: DAEMON - -# Java class to run -main_class: "${application.class}" - -domain: "${project.groupId}" - -short_description: "${project.artifactId}" - -# Platform launchers to generate (WINDOWS, LINUX, MAC_OSX) -# Linux launcher is suitable for Bourne shells (e.g. Linux/BSD) -platforms: [ LINUX ] - -# Working directory for app -# RETAIN will not change the working directory -# APP_HOME will change the working directory to the home of the app -# (where it was intalled) before running the main class -working_dir_mode: RETAIN - -# Minimum version of java required (system will be searched for acceptable jvm) -min_java_version: "1.8" - -# Min/max fixed memory (measured in MB) -min_java_memory: 512 -max_java_memory: 512 - -# Min/max memory by percentage of system -#min_java_memory_pct: 10 -#max_java_memory_pct: 20 - -# Try to create a symbolic link to java executable in <app_home>/run with -# the name of "<app_name>-java" so that commands like "ps" will make it -# easier to find your app -symlink_java: true diff --git a/src/main/java/com/chorem/bow/BowApp.java b/src/main/java/com/chorem/bow/BowApp.java deleted file mode 100644 index 6010bba..0000000 --- a/src/main/java/com/chorem/bow/BowApp.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.chorem.bow; - -import com.chorem.bow.model.BowUser; -import com.chorem.bow.repositories.BowUserRepository; -import com.chorem.bow.rest.BookmarkResources; -import com.chorem.bow.rest.BowUserResources; -import com.chorem.spgeed.SpgeedModule; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import io.jooby.Jooby; -import io.jooby.flyway.FlywayModule; -import io.jooby.hikari.HikariModule; -import io.jooby.json.JacksonModule; -import io.jooby.rocker.RockerModule; -import org.nuiton.spgeed.SqlSession; - -import java.util.List; -import java.util.UUID; - -/** - * @author jooby generator - */ -public class BowApp extends Jooby { - - { - assets("/static/?*"); - - install(new JacksonModule(JacksonModule.create() - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES))); - - install(new HikariModule()); - install(new FlywayModule()); - install(new SpgeedModule()); - install(new RockerModule()); - - new BowUserResources().use("/users", this); - new BookmarkResources().use("/bookmarks", this); - - get("/", ctx -> views.index.template("Rocker")); - - get("/listUsers", ctx -> { - List<BowUser> users; - try (SqlSession session = require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - users = repo.findAll(); - } - return views.listUsers.template(users); - }); - - get("/editUser", ctx -> { - BowUser user; - if (ctx.query("id").isMissing()) { - user = new BowUser(); - } else { - UUID id = ctx.query("id").to(UUID.class); - try (SqlSession session = require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - user = repo.find(id); - } - } - return views.editUser.template(user); - }); - - post("/editUser", ctx -> { - BowUser user = ctx.form(BowUser.class); - getLog().debug("upsert user: " + user); - try (SqlSession session = require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - BowUser upsert = repo.upsert(user); - ctx.sendRedirect("/"); - return upsert; - } - }); - - } - - public static void main(final String[] args) { - runApp(args, BowApp::new); - } - -} diff --git a/src/main/java/com/chorem/bow/BowContext.java b/src/main/java/com/chorem/bow/BowContext.java deleted file mode 100644 index 9a87ff8..0000000 --- a/src/main/java/com/chorem/bow/BowContext.java +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index d996f8b..0000000 --- a/src/main/java/com/chorem/bow/SortOrder.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.chorem.bow; - -public enum SortOrder { - ascVisit, descVisit, ascDate, descDate -} diff --git a/src/main/java/com/chorem/bow/model/Action.java b/src/main/java/com/chorem/bow/model/Action.java deleted file mode 100644 index fd1a445..0000000 --- a/src/main/java/com/chorem/bow/model/Action.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.chorem.bow.model; - -public class Action { - String prefix; - String action; - String suggest; -} diff --git a/src/main/java/com/chorem/bow/model/AuthenticationInfo.java b/src/main/java/com/chorem/bow/model/AuthenticationInfo.java deleted file mode 100644 index d088a43..0000000 --- a/src/main/java/com/chorem/bow/model/AuthenticationInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.chorem.bow.model; - -import lombok.Data; - -@Data -public class AuthenticationInfo { - String description; - 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; - String allowedChar; - String disallowedChar; - /** chaine ajouter en prefix du master password avant le hash */ - String salt; - /** chaine ajouter en suffix du hash final genere (le password genere peut donc etre plus long que maxLength) */ - String suffix; -} diff --git a/src/main/java/com/chorem/bow/model/Bookmark.java b/src/main/java/com/chorem/bow/model/Bookmark.java deleted file mode 100644 index 8640550..0000000 --- a/src/main/java/com/chorem/bow/model/Bookmark.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.chorem.bow.model; - -import lombok.Data; -import org.apache.commons.lang3.StringUtils; - -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.UUID; - -@Data -public class Bookmark { - - UUID id = UUID.randomUUID(); - UUID owner; - String uri; - String description; - LinkedHashSet<String> tags; - Date creationDate; - Date updateDate; - Date importDate; - LinkedHashSet<String> privateAlias; - LinkedHashSet<String> publicAlias; - AuthenticationInfo authenticationInfo; - byte[] favicon; - byte[] screenshot; - int visit; - String lang = "english"; - - public void setTagsAsString(String tagString) { - String[] tagArray = StringUtils.split(tagString); - tags = new LinkedHashSet<>(); - Collections.addAll(tags, tagArray); - } - - public void setPrivateAliasAsString(String privateAliasString) { - String[] privateAliasArray = StringUtils.split(privateAliasString); - privateAlias = new LinkedHashSet<>(); - Collections.addAll(privateAlias, privateAliasArray); - } - - public void setPublicAliasAsString(String publicAliasString) { - String[] publicAliasArray = StringUtils.split(publicAliasString); - publicAlias = new LinkedHashSet<>(); - Collections.addAll(publicAlias, publicAliasArray); - } -} diff --git a/src/main/java/com/chorem/bow/model/BowUser.java b/src/main/java/com/chorem/bow/model/BowUser.java deleted file mode 100644 index 453ef53..0000000 --- a/src/main/java/com/chorem/bow/model/BowUser.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.chorem.bow.model; - -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; - -@Data -public class BowUser { - UUID id = UUID.randomUUID(); - Date creationDate; - Date updateDate; - String login; - String password; - List<Token> tokens; - LinkedHashSet<String> emails; - /* en cle l'email, en valeur une chaine random (uuid) qui permet la validation */ - Map<String, String> unconfirmedEmails; - AuthenticationInfo authenticationInfo; - boolean autoScreenshot; - boolean autoFavicon; - int maxTagInCloud = 25; - int maxResult = 50; - HashSet<Action> actions; -} diff --git a/src/main/java/com/chorem/bow/model/Token.java b/src/main/java/com/chorem/bow/model/Token.java deleted file mode 100644 index 5c87f67..0000000 --- a/src/main/java/com/chorem/bow/model/Token.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 8e78f99..0000000 --- a/src/main/java/com/chorem/bow/repositories/BookmarkRepository.java +++ /dev/null @@ -1,102 +0,0 @@ -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)" + - " RETURNING *") - Bookmark insert(Bookmark bookmark); - - @Update(sql = - "UPDATE bookmark AS t" + - " SET (owner, uri, description, privateAlias, publicAlias, authenticationInfo, lang) = " + - " (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" + - " SET authenticationInfo = (SELECT * FROM json_populate_record(NULL::AuthenticationInfo, ${info |json()}::json))" + - " WHERE id=${id}" + - " RETURNING *") - Bookmark updateAuthenticationInfo(UUID id, AuthenticationInfo info); - - @Update(sql = "UPDATE bookmark AS t" + - " SET privateAlias = ${alias |array()}" + - " WHERE id=${id}" + - " RETURNING *") - Bookmark updatePrivateAlias(UUID id, Collection alias); - - @Update(sql = "UPDATE bookmark AS t" + - " SET publicAlias = ${alias |array()}" + - " WHERE id=${id}" + - " RETURNING *") - Bookmark updatePublicAlias(UUID id, Collection alias); - - @Update(sql = "UPDATE bookmark AS t" + - " SET visit = visit + 1" + - " WHERE id=${idOrAlias}::uuid OR privateAlias @> ARRAY[${idOrAlias}] OR publicAlias @> ARRAY[${idOrAlias}]" + - " RETURNING uri") - String visit(String idOrAlias); - - @Select(sql = "select * from bookmark") - List<Bookmark> findAll(); - - @Select(sql = "select * from bookmark where id=${uuid}::uuid") - Bookmark find(UUID uuid); - - @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/repositories/BowUserRepository.java b/src/main/java/com/chorem/bow/repositories/BowUserRepository.java deleted file mode 100644 index 75db804..0000000 --- a/src/main/java/com/chorem/bow/repositories/BowUserRepository.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.chorem.bow.repositories; - -import com.chorem.bow.model.AuthenticationInfo; -import com.chorem.bow.model.Bookmark; -import com.chorem.bow.model.BowUser; -import org.nuiton.spgeed.annotations.Select; -import org.nuiton.spgeed.annotations.Update; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public interface BowUserRepository { - - @Update(sql = - "INSERT INTO bowUser AS t" + - " SELECT * FROM json_populate_record(NULL::bowUser, ${user |json()}::json)" + - " ON CONFLICT (id) DO UPDATE SET" + - " login = EXCLUDED.login," + - " password = EXCLUDED.password," + - " tokens = EXCLUDED.tokens," + - " emails = EXCLUDED.emails," + - " unconfirmedEmails = EXCLUDED.unconfirmedEmails," + - " authenticationInfo = EXCLUDED.authenticationInfo," + - " autoScreenshot = EXCLUDED.autoScreenshot," + - " autoFavicon = EXCLUDED.autoFavicon," + - " maxTagInCloud = EXCLUDED.maxTagInCloud," + - " maxResult = EXCLUDED.maxResult," + - " actions = EXCLUDED.actions" + - " RETURNING *") - BowUser upsert(BowUser user); - - @Update(sql = - "INSERT INTO bowUser AS t" + - " SELECT * FROM json_populate_record(NULL::bowUser, ${user |json()}::json)" + - " RETURNING *") - BowUser insert(BowUser user); - - @Update(sql = - "UPDATE bowUser AS t" + - " SET (login, password, tokens, emails, unconfirmedEmails, authenticationInfo, autoScreenshot, autoFavicon, maxTagInCloud, maxResult, actions) = " + - " (SELECT login, password, tokens, emails, unconfirmedEmails, authenticationInfo, autoScreenshot, autoFavicon, maxTagInCloud, maxResult, actions FROM json_populate_record(NULL::bowUser, ${user |json()}::json))" + - " RETURNING *") - BowUser update(BowUser user); - - @Update(sql = "UPDATE bowUser AS t" + - " SET authenticationInfo = (SELECT * FROM json_populate_record(NULL::AuthenticationInfo, ${info |json()}::json))" + - " WHERE id=${id}" + - " RETURNING *") - BowUser updateAuthenticationInfo(UUID id, AuthenticationInfo info); - - @Update(sql = "UPDATE bowUser AS t" + - " SET emails = ${emails |array()}" + - " WHERE id=${id}" + - " RETURNING *") - BowUser updateEmails(UUID id, Collection emails); - - @Select(sql = "SELECT * FROM bowUser") - List<BowUser> findAll(); - - @Select(sql = "SELECT * FROM bowUser WHERE id=${uuid}::uuid") - BowUser find(UUID uuid); - - @Select(sql = "SELECT * FROM bowUser WHERE login=${loginOrEmail} or email ? ${loginOrEmail}") - BowUser find(String loginOrEmail); - - @Update(sql = "DELETE FROM bowUser WHERE id=${id} RETURNING *") - BowUser delete(UUID id); - -} diff --git a/src/main/java/com/chorem/bow/rest/BookmarkResources.java b/src/main/java/com/chorem/bow/rest/BookmarkResources.java deleted file mode 100644 index 047aae8..0000000 --- a/src/main/java/com/chorem/bow/rest/BookmarkResources.java +++ /dev/null @@ -1,94 +0,0 @@ -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); - Chunk<Bookmark> chunk = repo.find(restart(fetch, next), tags, q, createFacet(50)); - return chunk; - } - }); - - app.get("/{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.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.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/java/com/chorem/bow/rest/BowUserResources.java b/src/main/java/com/chorem/bow/rest/BowUserResources.java deleted file mode 100644 index c74aabd..0000000 --- a/src/main/java/com/chorem/bow/rest/BowUserResources.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.chorem.bow.rest; - -import com.chorem.bow.model.BowUser; -import com.chorem.bow.repositories.BowUserRepository; -import io.jooby.Jooby; -import io.jooby.StatusCode; -import org.nuiton.spgeed.SqlSession; - -import java.util.Set; -import java.util.UUID; - -public class BowUserResources { //extends Jooby { - - { -// use("/", this); - } - - public void use(String path, Jooby app){ - app.path(path, () -> { - app.get("/", ctx -> { - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - return repo.findAll(); - } - }); - - app.get("/{id}", ctx -> { - UUID id = ctx.path("id").to(UUID.class); - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - BowUser bowUser = repo.find(id); - if (bowUser == null) { - ctx.setResponseCode(StatusCode.NOT_FOUND); - } - return bowUser; - } - }); - - app.post("/", ctx -> { - BowUser user = ctx.body(BowUser.class); - app.getLog().debug("insert user: " + user); - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - return repo.insert(user); - } - }); - - app.put("/{id}", ctx -> { - UUID id = ctx.path("id").to(UUID.class); - BowUser user = ctx.body(BowUser.class); - user.setId(id); - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - return repo.update(user); - } - }); - - app.put("/{id}/emails", ctx -> { - UUID id = ctx.path("id").to(UUID.class); - Set emails = ctx.body(Set.class); - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - return repo.updateEmails(id, emails); - } - }); - - app.delete("/{id}", ctx -> { - UUID id = ctx.path("id").to(UUID.class); - try (SqlSession session = app.require(SqlSession.class)) { - BowUserRepository repo = session.getDao(BowUserRepository.class); - BowUser bowUser = repo.delete(id); - if (bowUser == null) { - ctx.setResponseCode(StatusCode.NOT_FOUND); - } - return bowUser; - } - }); - - }); - } -} diff --git a/src/main/java/com/chorem/spgeed/SpgeedModule.java b/src/main/java/com/chorem/spgeed/SpgeedModule.java deleted file mode 100644 index 4e5eae9..0000000 --- a/src/main/java/com/chorem/spgeed/SpgeedModule.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.chorem.spgeed; - -import io.jooby.Extension; -import io.jooby.Jooby; -import io.jooby.ServiceKey; -import io.jooby.ServiceRegistry; -import org.nuiton.spgeed.SqlSession; - -import javax.annotation.Nonnull; -import javax.inject.Provider; -import javax.sql.DataSource; - -public class SpgeedModule implements Extension { - - private String name; - - /** - * Creates a new SpgeedModule module using the <code>db</code> property key. This key must be - * present in the application configuration file, like: - * - * <pre>{@code - * db.url = "jdbc:url" - * db.user = dbuser - * db.password = dbpass - * }</pre> - */ - public SpgeedModule() { - this("db"); - } - - /** - * Creates a new SpgeedModule module. - * - * @param name The name/key of the data source to attach. - */ - public SpgeedModule(@Nonnull String name) { - this.name = name; - } - - @Override - public void install(@Nonnull Jooby application) throws Exception { - ServiceRegistry registry = application.getServices(); - - DataSource datasource = findDataSource(registry); - - Provider<SqlSession> provider = new SqlSessionProvider(datasource); - - registry.putIfAbsent(ServiceKey.key(SqlSession.class), provider); - registry.put(ServiceKey.key(SqlSession.class, name), provider); - } - - private DataSource findDataSource(@Nonnull ServiceRegistry registry) { - DataSource dataSource = registry.getOrNull(ServiceKey.key(DataSource.class, name)); - if (dataSource == null) { - // TODO: replace with usage exception - dataSource = registry.require(DataSource.class); - } - return dataSource; - } -} diff --git a/src/main/java/com/chorem/spgeed/SqlSessionProvider.java b/src/main/java/com/chorem/spgeed/SqlSessionProvider.java deleted file mode 100644 index 29430a7..0000000 --- a/src/main/java/com/chorem/spgeed/SqlSessionProvider.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.chorem.spgeed; - -import org.nuiton.spgeed.SqlSession; - -import javax.inject.Provider; -import javax.sql.DataSource; - -public class SqlSessionProvider implements Provider<SqlSession> { -private DataSource datasource; - - public SqlSessionProvider(DataSource datasource) { - this.datasource = datasource; - } - - @Override public SqlSession get() { - SqlSession result = new SqlSession(datasource); - return result; - } -} diff --git a/src/main/resources/static/css/global.css b/src/main/resources/static/css/global.css deleted file mode 100644 index 51a6feb..0000000 --- a/src/main/resources/static/css/global.css +++ /dev/null @@ -1,773 +0,0 @@ -@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 deleted file mode 100644 index 49de5d9..0000000 Binary files a/src/main/resources/static/img/favicon.png and /dev/null differ diff --git a/src/main/templates/views/editUser.rocker.html b/src/main/templates/views/editUser.rocker.html deleted file mode 100644 index 77b3ff0..0000000 --- a/src/main/templates/views/editUser.rocker.html +++ /dev/null @@ -1,25 +0,0 @@ -@import com.chorem.bow.model.BowUser - -@args (BowUser user) - -@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> - password: <input type="password" name="password" value="@?user.getPassword()"><br> - autoScreenshot: <input type="checkbox" name="autoScreenshot" @(user != null && user.isAutoScreenshot() ? "CHECKED" : "")><br> - autoFavicon: <input type="checkbox" name="autoFavicon" @(user != null && user.isAutoFavicon() ? "CHECKED" : "")><br> - maxTagInCloud: <input type="number" name="maxTagInCloud" value="@?user.getMaxTagInCloud()"><br> - maxResult: <input type="number" name="maxResult" value="@?user.getMaxResult()"><br> - - <!-- - 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; - AuthenticationInfo authenticationInfo; - HashSet<Action> actions; ---> - <input type="submit" name="Save"> -</form> -} \ No newline at end of file diff --git a/src/main/templates/views/index.rocker.html b/src/main/templates/views/index.rocker.html deleted file mode 100644 index 0c4ebcc..0000000 --- a/src/main/templates/views/index.rocker.html +++ /dev/null @@ -1,5 +0,0 @@ -@args (String message) - -<p>Hello @message!</p> -<a href="/editUser">New User</a> -<a href="/listUsers">List Users</a> \ No newline at end of file diff --git a/src/main/templates/views/layout.rocker.html b/src/main/templates/views/layout.rocker.html deleted file mode 100644 index 1f4406d..0000000 --- a/src/main/templates/views/layout.rocker.html +++ /dev/null @@ -1,106 +0,0 @@ -@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 deleted file mode 100644 index 8973dd0..0000000 --- a/src/main/templates/views/listUsers.rocker.html +++ /dev/null @@ -1,10 +0,0 @@ -@import com.chorem.bow.model.BowUser -@import java.util.Collection; - -@args (Collection<BowUser> users) - -@views.layout.template("List users", RockerContent.NONE, RockerContent.NONE) -> { - @for (user : users) { - <li><a href="/editUser?id=@user.getId()">@?user.getLogin()</a></li> - } -} \ No newline at end of file diff --git a/src/main/templates/views/search.rocker.html b/src/main/templates/views/search.rocker.html deleted file mode 100644 index 98da7db..0000000 --- a/src/main/templates/views/search.rocker.html +++ /dev/null @@ -1,72 +0,0 @@ -@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/BowAppTest.java b/src/test/java/com/chorem/bow/BowAppTest.java deleted file mode 100644 index be605f3..0000000 --- a/src/test/java/com/chorem/bow/BowAppTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.chorem.bow; - -import io.jooby.MockRouter; -import io.jooby.StatusCode; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class BowAppTest { -// -// @Test -// public void testHome() throws Throwable { -// MockRouter router = new MockRouter(new BowApp()); -// router.get("/", response -> { -// Assertions.assertEquals(StatusCode.OK, response.getStatusCode()); -// Assertions.assertEquals("Hello World!", response.value(String.class)); -// }); -// } - -} diff --git a/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java b/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java deleted file mode 100644 index d3907a8..0000000 --- a/src/test/java/com/chorem/bow/rest/BookmarkResourcesTest.java +++ /dev/null @@ -1,126 +0,0 @@ -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()); - } - - } - - -} diff --git a/src/test/java/com/chorem/bow/rest/BowUserResourcesTest.java b/src/test/java/com/chorem/bow/rest/BowUserResourcesTest.java deleted file mode 100644 index 43ed7a9..0000000 --- a/src/test/java/com/chorem/bow/rest/BowUserResourcesTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.chorem.bow.rest; - -import com.chorem.bow.BowApp; -import com.chorem.bow.model.AuthenticationInfo; -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; - -@JoobyTest(BowApp.class) -public class BowUserResourcesTest { - - 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 { - BowUser user = new BowUser(); - user.setLogin("poussin"); - user.setPassword("xxxxxxxx"); - user.setEmails(new LinkedHashSet<>(Arrays.asList("bpoussin.free.fr", "poussin@codelutin.com"))); - - System.out.println("Creation user"); - // CREATION - String userAsJson = jsonMapper.writeValueAsString(user); - RequestBody body = RequestBody.create(userAsJson, MediaType.get("application/json")); - Request request = new Request.Builder() - .url(serverPath + "users") - .post(body) - .build(); - - try (Response response = client.newCall(request).execute()) { - Assertions.assertEquals(StatusCode.OK.value(), response.code()); - user = jsonMapper.readValue(response.body().string(), BowUser.class); - } - - System.out.println("Update user"); - // UPDATE - AuthenticationInfo authenticationInfo = new AuthenticationInfo(); - authenticationInfo.setLogin("bpoussin"); - user.setAuthenticationInfo(authenticationInfo); - userAsJson = jsonMapper.writeValueAsString(user); - body = RequestBody.create(userAsJson, MediaType.get("application/json")); - request = new Request.Builder() - .url(serverPath + "users/" + user.getId()) - .put(body) - .build(); - - try (Response response = client.newCall(request).execute()) { - Assertions.assertEquals(StatusCode.OK.value(), response.code()); - user = jsonMapper.readValue(response.body().string(), BowUser.class); - } - - System.out.println("Update user email"); - // UPDATE EMAIL - userAsJson = jsonMapper.writeValueAsString(Arrays.asList("poussin@toto.com")); - body = RequestBody.create(userAsJson, MediaType.get("application/json")); - request = new Request.Builder() - .url(serverPath + "users/" + user.getId() + "/emails") - .put(body) - .build(); - - try (Response response = client.newCall(request).execute()) { - Assertions.assertEquals(StatusCode.OK.value(), response.code()); - user = jsonMapper.readValue(response.body().string(), BowUser.class); - } - - System.out.println("Delete user"); - // DELETE - request = new Request.Builder() - .url(serverPath + "users/" + user.getId()) - .delete() - .build(); - - try (Response response = client.newCall(request).execute()) { - Assertions.assertEquals(StatusCode.OK.value(), response.code()); - user = jsonMapper.readValue(response.body().string(), BowUser.class); - } - - System.out.println("Get user"); - // GET - request = new Request.Builder() - .url(serverPath + "users/" + user.getId()) - .get() - .build(); - - try (Response response = client.newCall(request).execute()) { - Assertions.assertEquals(StatusCode.NOT_FOUND.value(), response.code()); - } - - } - - -} diff --git a/src/test/java/com/chorem/bow/rest/RestHelper.java b/src/test/java/com/chorem/bow/rest/RestHelper.java deleted file mode 100644 index 36eab60..0000000 --- a/src/test/java/com/chorem/bow/rest/RestHelper.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.chorem.bow.rest; - -import io.jooby.MockContext; -import lombok.experimental.UtilityClass; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -@UtilityClass -public class RestHelper { - void addHeader(MockContext ctx, String name, String value) { - Map<String, List<String>> current = ctx.headerMultimap(); - List<String> values = current.get(name); - if (values == null) { - values = Arrays.asList(value); - current.put(name, values); - } else { - values.add(value); - } - - Object result = current; - ctx.setHeaders((Map<String, Collection<String>>)result); - } -} diff --git a/utils/password.go b/utils/password.go new file mode 100644 index 0000000..ad2c6f5 --- /dev/null +++ b/utils/password.go @@ -0,0 +1,37 @@ +/* +Package utils contient des choses réutilisable +*/ +package utils + +import ( + "crypto/md5" + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +/* +HashPassword return hash for the given password +currently bcrypt is used +*/ +func HashPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte([]byte(password)), 8) + return string(hashedPassword), err +} + +/* +CheckPassword compare password and hash, return true if password proceduce this hash. +Compare is done on all version of password encoding in this order: +- bcrypt +- md5 +*/ +func CheckPassword(password string, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + if err != nil { + // try with old md5 + md5hash := md5.Sum([]byte(password)) + return fmt.Sprintf("%x", md5hash) == hash + } + + return true +} diff --git a/utils/uuid.go b/utils/uuid.go new file mode 100644 index 0000000..1d98e7b --- /dev/null +++ b/utils/uuid.go @@ -0,0 +1,27 @@ +/* +Package utils contient des choses réutilisable +*/ +package utils + +import ( + "io/ioutil" + "strings" +) + +/* +GenUUID generate uuid. + +Permet de generer des uuid sur une plateforme linux qui +est la cible de deploiement + +si on vise d'autre plateforme, il faut remplacer cette +implantation par https://github.com/google/uuid +*/ +func GenUUID() (string, error) { + u, err := ioutil.ReadFile("/proc/sys/kernel/random/uuid") + if err != nil { + return "", err + } + + return strings.TrimSpace(string(u)), nil +} -- 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-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit e16d8f0919c72d202e3ac7e5452feb4895d811b6 Author: Benjamin <poussin@codelutin.com> Date: Mon Apr 6 01:33:37 2020 +0200 ajout de la migration de base automatique --- Dockerfile | 5 +- cmd/bow/main.go | 2 +- doc/implementation.md | 8 +++ go.mod | 5 ++ go.sum | 129 +++++++++++++++++++++++++++++++++++++++++ migrate/001_init_schema.sql | 56 ++++++++++++------ migrate/002_migration_data.sql | 8 +++ repository/database.go | 116 +++++++++++++++++++++++++++++++++++- repository/userRepository.go | 40 +++++++++---- 9 files changed, 337 insertions(+), 32 deletions(-) diff --git a/Dockerfile b/Dockerfile index 911878d..91712b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,12 @@ FROM golang:alpine AS builder WORKDIR $GOPATH COPY cmd cmd -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o /bin/bow cmd/bow/main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o /bow/bow cmd/bow/main.go +RUN cp -a migrate /bow FROM gcr.io/distroless/static:nonroot -COPY --from=builder /bin/bow / +COPY --from=builder /bow/* / # Expose port 8080 to the outside world EXPOSE 8080 diff --git a/cmd/bow/main.go b/cmd/bow/main.go index 1f6b15c..20fba7c 100644 --- a/cmd/bow/main.go +++ b/cmd/bow/main.go @@ -10,7 +10,7 @@ import ( func main() { databaseURL := os.Getenv("DATABASE_URL") - log.Println("Init database", databaseURL) + log.Println("Init database") repository.Init(databaseURL) // u := model.BowUser{ID: utils.GenUUID(), Login: "bpoussin", Emails: []string{"benjamin@pouss.in"}, Password: "toto"} diff --git a/doc/implementation.md b/doc/implementation.md index 5a7bff9..6d7ade8 100644 --- a/doc/implementation.md +++ b/doc/implementation.md @@ -1,3 +1,11 @@ +== Optimisation Posggresql + +Les tableaux (ex: text[]) utilise des index gin, il faut donc utiliser seulement +les opérateurs de tableau pour faire les comparaisons dans les where, sinon +l'index n'est pas utilisé + +Il faut remplacer `'mon@email' = ANY(emails)` par `emails @> '{"mon@email"}'::Text[]` + == Group Il est possible dans les tags de mettre un `@` devant un tag pour indiquer un diff --git a/go.mod b/go.mod index 09c369a..b8aff7e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,11 @@ require ( github.com/gorilla/mux v1.7.4 github.com/jackc/pgtype v1.3.0 github.com/jackc/pgx/v4 v4.6.0 + github.com/jackc/tern v1.10.2 + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/cobra v0.0.7 // indirect + github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 + golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect ) diff --git a/go.sum b/go.sum index eea20df..30d1eb2 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,51 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -16,8 +54,11 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o= github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgerrcode v0.0.0-20190803225404-afa3381909a6 h1:geJ1mgTGd0WQo67wEd+H4OjFG5uA2e3cEBz9D5+pftU= +github.com/jackc/pgerrcode v0.0.0-20190803225404-afa3381909a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -36,6 +77,7 @@ github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0= github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= @@ -43,14 +85,22 @@ github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGk github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/tern v1.10.2 h1:lp93SpKdNgG5oiPSjUOojUQrO/zp33+lnh0F40htYgA= +github.com/jackc/tern v1.10.2/go.mod h1:2kyvgnBxcGl3YTwrpD1nZpLEti3zFUkhx7Y6zvDfXP0= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= @@ -58,21 +108,60 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20160114030619-9c9300901990 h1:VyhPAPSAKafhltIFZATJWdmFG0lZKoIGAKfTuZwEHfs= +github.com/spf13/cobra v0.0.0-20160114030619-9c9300901990/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20151218134703-7f60f83a2c81 h1:e8OMOPK+iXlzdnq5GOtSZDnw9HJi1faEKhCoEIxVUrY= +github.com/spf13/pflag v0.0.0-20151218134703-7f60f83a2c81/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -80,45 +169,85 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk= golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/migrate/001_init_schema.sql b/migrate/001_init_schema.sql index c1ec3d8..2a82029 100644 --- a/migrate/001_init_schema.sql +++ b/migrate/001_init_schema.sql @@ -12,13 +12,13 @@ CREATE OR REPLACE FUNCTION text(citext[]) returns text language sql immutable as $$ select $1::text $$; -create type Token as ( +CREATE TYPE Token AS ( name TEXT, token UUID, validity timestamp ); -create type AuthenticationInfo as ( +CREATE TYPE AuthenticationInfo AS ( description text, login text, form text, @@ -31,13 +31,13 @@ create type AuthenticationInfo as ( suffix text -- chaine ajouter en suffix du hash final genere (le password genere peut donc etre plus long que maxLength) ); -create type Action as ( +CREATE TYPE Action AS ( prefix TEXT, action Text, suggest Text ); -create table bowUser ( +CREATE TABLE bowUser ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creationDate timestamp DEFAULT current_timestamp, updateDate timestamp DEFAULT current_timestamp, @@ -56,10 +56,10 @@ create table bowUser ( CREATE UNIQUE INDEX bowUser_login_idx ON BowUser (login); CREATE INDEX bowUser_token_idx ON BowUser USING gin (tokens); -CREATE UNIQUE INDEX bowUser_emails_idx ON BowUser (unnest(emails)); +CREATE INDEX bowUser_emails_idx ON BowUser USING gin (emails); -- TODO garantir l'unicite avant l'insertion CREATE INDEX bowUser_unconfirmedEmails_idx ON BowUser USING gin (unconfirmedEmails); -create table group ( +CREATE TABLE bowgroup ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creationDate timestamp DEFAULT current_timestamp, updateDate timestamp DEFAULT current_timestamp, @@ -69,15 +69,15 @@ create table group ( admin UUID[], writer UUID[], reader UUID[] -} +); -CREATE INDEX group_name_idx ON group (name); -CREATE INDEX group_token_idx ON group USING gin (tokens); -CREATE INDEX group_admin_idx ON group (unnest(admin)); -CREATE INDEX group_writer_idx ON group (unnest(writer)); -CREATE INDEX group_reader_idx ON group (unnest(reader)); +CREATE INDEX bowgroup_name_idx ON bowgroup (name); +CREATE INDEX bowgroup_token_idx ON bowgroup USING gin (tokens); +CREATE INDEX bowgroup_admin_idx ON bowgroup USING gin (admin); +CREATE INDEX bowgroup_writer_idx ON bowgroup USING gin (writer); +CREATE INDEX bowgroup_reader_idx ON bowgroup USING gin (reader); -create table bookmark ( +CREATE TABLE bookmark ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner UUID REFERENCES bowUser(id) ON DELETE CASCADE ON UPDATE CASCADE, uri TEXT, @@ -100,10 +100,10 @@ CREATE INDEX bookmark_tags_idx ON bookmark USING gin (tags); -- index pour la r CREATE INDEX bookmark_tags_like_idx ON bookmark USING gin (text(tags) gin_trgm_ops); -- index pour la recherche via ilike "%toto%" CREATE INDEX bookmark_description_idx ON bookmark USING GIN (to_tsvector(lang, description)); CREATE INDEX bookmark_privateAlias_idx ON bookmark USING gin (privateAlias); -CREATE UNIQUE INDEX bookmark_publicAlias_idx ON bookmark USING gin (publicAlias); +CREATE INDEX bookmark_publicAlias_idx ON bookmark USING gin (publicAlias); -- TODO garantir l'unicité -- penser au nettoyage de cette table, si plus aucun bookmark ne reference cette uri -create table pageHistory ( +CREATE TABLE pageHistory ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), uri TEXT, creationDate TIMESTAMP, @@ -114,7 +114,7 @@ create table pageHistory ( CREATE INDEX pageHistory_uri_creationDate_idx ON pageHistory(uri, creationDate); CREATE INDEX pageHistory_content_idx ON pageHistory USING GIN (to_tsvector(lang, content)); -create table actionHistory ( +CREATE TABLE actionHistory ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner UUID REFERENCES bowUser(id) ON DELETE CASCADE ON UPDATE CASCADE, creationDate TIMESTAMP, @@ -146,11 +146,31 @@ END; $$ language 'plpgsql'; CREATE TRIGGER update_BowUser_createDate BEFORE INSERT ON BowUser FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); -CREATE TRIGGER update_group_createDate BEFORE INSERT ON group FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); +CREATE TRIGGER update_bowgroup_createDate BEFORE INSERT ON bowgroup FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_Bookmark_createDate BEFORE INSERT ON Bookmark FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_PageHistory_createDate BEFORE INSERT ON pageHistory FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_ActionHistory_createDate BEFORE INSERT ON actionHistory FOR EACH ROW EXECUTE PROCEDURE update_creationDate_column(); CREATE TRIGGER update_BowUser_updateDate BEFORE INSERT OR UPDATE ON BowUser FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); -CREATE TRIGGER update_group_updateDate BEFORE INSERT OR UPDATE ON group FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); +CREATE TRIGGER update_bowgroup_updateDate BEFORE INSERT OR UPDATE ON bowgroup FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); CREATE TRIGGER update_Bookmark_updateDate BEFORE INSERT OR UPDATE ON Bookmark FOR EACH ROW EXECUTE PROCEDURE update_updateDate_column(); + +---- create above / drop below ---- + +DROP TABLE actionHistory; +DROP TABLE pageHistory; +DROP TABLE bookmark; +DROP TABLE bowgroup; +DROP TABLE bowUser; + +DROP TYPE AuthenticationInfo; +DROP TYPE Action; +DROP TYPE Token; + +DROP FUNCTION text(citext[]); +DROP FUNCTION update_creationDate_column; +DROP FUNCTION update_updateDate_column; + +DROP EXTENSION IF EXISTS "pgcrypto"; +DROP EXTENSION IF EXISTS "pg_trgm"; +DROP EXTENSION IF EXISTS "citext"; \ No newline at end of file diff --git a/migrate/002_migration_data.sql b/migrate/002_migration_data.sql index 2d9cd71..dcf51ba 100644 --- a/migrate/002_migration_data.sql +++ b/migrate/002_migration_data.sql @@ -133,3 +133,11 @@ update bowUser set creationDate = (select min(creationDate) from bookmark where -- reactivate trigger ALTER TABLE BowUser ENABLE TRIGGER update_BowUser_createDate; ALTER TABLE Bookmark ENABLE TRIGGER update_Bookmark_createDate; + +---- create above / drop below ---- + +DELETE FROM actionHistory; +DELETE FROM pageHistory; +DELETE FROM bookmark; +DELETE FROM group; +DELETE FROM bowUser; diff --git a/repository/database.go b/repository/database.go index b066216..6f10dea 100644 --- a/repository/database.go +++ b/repository/database.go @@ -2,24 +2,138 @@ package repository import ( "context" + "fmt" "log" + "os" + "os/signal" + "strings" + "time" + "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" + "github.com/jackc/tern/migrate" ) var db *pgxpool.Pool +type errorLineExtract struct { + LineNum int // Line number starting with 1 + ColumnNum int // Column number starting with 1 + Text string // Text of the line without a new line character. +} + + /* Init initialise la connexion a la base en utilisant */ func Init(databaseURL string) { + migrateDatabase(databaseURL) + poolConfig, err := pgxpool.ParseConfig(databaseURL) if err != nil { log.Fatalln("Unable to parse DATABASE_URL", "error", err) } - db, err = pgxpool.ConnectConfig(context.Background(), poolConfig) + ctx := context.Background() + db, err = pgxpool.ConnectConfig(ctx, poolConfig) if err != nil { log.Fatalln("Unable to create connection pool", databaseURL, "error", err) } + +} + +// extractErrorLine takes source and character position extracts the line +// number, column number, and the line of text. +// +// The first character is position 1. +func extractErrorLine(source string, position int) (errorLineExtract, error) { + ele := errorLineExtract{LineNum: 1} + + if position > len(source) { + return ele, fmt.Errorf("position (%d) is greater than source length (%d)", position, len(source)) + } + + lines := strings.SplitAfter(source, "\n") + for _, ele.Text = range lines { + if position-len(ele.Text) < 1 { + ele.ColumnNum = position + break + } + + ele.LineNum++ + position -= len(ele.Text) + } + + ele.Text = strings.TrimSuffix(ele.Text, "\n") + + return ele, nil +} + +func migrateDatabase(databaseURL string) { + ctx := context.Background() + conn, err := pgx.Connect(ctx, databaseURL) + if err != nil { + log.Fatalf("Error initializing database connexion:\n %v\n", err) + } + defer conn.Close(ctx) + + migrator, err := migrate.NewMigrator(ctx, conn, "public.schema_version") + if err != nil { + log.Fatalf("Error initializing migrator:\n %v\n", err) + } + + err = migrator.LoadMigrations("migrate") + if err != nil { + log.Fatalf("Error loading migrations:\n %v\n", err) + } + if len(migrator.Migrations) == 0 { + log.Fatalf("No migrations found") + } + + migrator.OnStart = func(sequence int32, name, direction, sql string) { + log.Printf("%s executing %s %s\n%s\n\n", time.Now().Format("2006-01-02 15:04:05"), name, direction, sql) + } + + var currentVersion int32 + currentVersion, err = migrator.GetCurrentVersion(ctx) + if err != nil { + log.Fatalf("Unable to get current version:\n %v\n", err) + } + log.Printf("Migrate database from %s to last version\n", currentVersion) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + interruptChan := make(chan os.Signal, 1) + signal.Notify(interruptChan, os.Interrupt) + go func() { + <-interruptChan + cancel() // Cancel any in progress migrations + signal.Reset() // Only listen for one interrupt. If another interrupt signal is received allow it to terminate the program. + }() + + err = migrator.Migrate(ctx) + + if err != nil { + log.Println(err) + + if err, ok := err.(migrate.MigrationPgError); ok { + if err.Detail != "" { + log.Println("DETAIL:", err.Detail) + } + + if err.Position != 0 { + ele, err := extractErrorLine(err.Sql, int(err.Position)) + if err != nil { + log.Fatalf("Can't find line error:\n %v\n", err) + } + + prefix := fmt.Sprintf("LINE %d: ", ele.LineNum) + log.Printf("%s%s\n", prefix, ele.Text) + + padding := strings.Repeat(" ", len(prefix)+ele.ColumnNum-1) + log.Printf("%s^\n", padding) + } + } + os.Exit(1) + } } diff --git a/repository/userRepository.go b/repository/userRepository.go index 9f29796..60aa042 100644 --- a/repository/userRepository.go +++ b/repository/userRepository.go @@ -16,9 +16,9 @@ import ( /* UserJSON retourne l'utilisateur au format json */ -func UserJSON(loginOrEmail string) (string, error) { +func UserJSON(id string) (string, error) { var pgjson pgtype.JSON - row := db.QueryRow(context.Background(), `WITH __all AS (select * from bowuser where login=$1 or $1 = ANY(emails)) SELECT json_agg(__all.*) as j FROM __all`, loginOrEmail) + row := db.QueryRow(context.Background(), `WITH __all AS (select * from bowuser where id=$1) SELECT json_agg(__all.*) as j FROM __all`, id) err := row.Scan(&pgjson) if err != nil { return "", err @@ -31,17 +31,28 @@ func UserJSON(loginOrEmail string) (string, error) { } /* -UserJSON retourne l'utilisateur au format json +UserJSON retourne l'id de l'utilisateur (string) si le mot de passe est le bon et que l'utilisateur est retrouve */ -func checkPassword(idOrLoginOrEmail string, password string) bool { +func checkPassword(loginOrEmail string, password string) string, error { + var uuid pgtype.UUID var hash string - row := db.QueryRow(context.Background(), `select password from bowuser where id=$1 or login=$1 or $1 = ANY(emails)`, idOrLoginOrEmail) - err := row.Scan(&hash) + row := db.QueryRow(context.Background(), `select id, password from bowuser where login=$1 or emails @> $2::text[]`, loginOrEmail, fmt.Sprintf(`{"%s"}`, loginOrEmail)) + err := row.Scan(&uuid, &hash) if err != nil { - return false + return "", err + } + + if !utils.CheckPassword(password, hash) { + return "", fmt.Errorf("Password mismatch for %s", loginOrEmail) } - return utils.CheckPassword(password, hash) + var result string + err = pgjson.AssignTo(&result) + if err != nil { + return "", err + } + + return result } /* @@ -91,7 +102,14 @@ func DeleteUser(id string) error { UpdateUserPassword update user password, if old password match, or if force is true */ func UpdateUserPassword(id string, password string, oldPassword string, force bool) error { - if force || checkPassword(id, oldPassword) { + var hash string + row := db.QueryRow(context.Background(), `select password from bowuser where id=$1`, id) + err := row.Scan(&hash) + if err != nil { + return err + } + + if force || utils.CheckPassword(oldPassword, hash) { hash, err := utils.HashPassword(password) @@ -252,6 +270,7 @@ func ConfirmUserEmail(id string, token string) error { // - on ajoute la position de cette email (numero de ligne) // - on ne garde que la ligne de l'email concerné // - on update la ligne en faisant passer l'email dans les emails valides (ajout dans emails, suppression dans unconfirmedemails) + // - on garanti que personne n'a deja cette email, sinon on ne fait rien (il reste en a confirmer) jsonPathToCheckToken := fmt.Sprintf(`$[*].token ? (@ == "%s")`, token) modif, err := db.Exec(context.Background(), ` with ue as (select jsonb_array_elements(unconfirmedemails) as json @@ -267,7 +286,8 @@ func ConfirmUserEmail(id string, token string) error { emails=array_append(b.emails, data.email), unconfirmedemails=unconfirmedemails - (data.index::int - 1) from data - where b.id=$1 and b.unconfirmedemails @? $3;`, id, token, jsonPathToCheckToken) + where b.id=$1 and b.unconfirmedemails @? $3 and + (select count(id) from bowuser where emails @> ('{' || data.email || '}')::text[]) = 0;`, id, token, jsonPathToCheckToken) if modif.RowsAffected() != 1 { return errors.New("No user found to for this token") -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
participants (1)
-
chorem.org scm