diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8fecc9a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nim/htmlutils"] + path = nim/htmlutils + url = ssh://git@nuc/mnt/git/htmlutils diff --git a/nim/build.nims b/nim/build.nims new file mode 100755 index 0000000..c957746 --- /dev/null +++ b/nim/build.nims @@ -0,0 +1,5 @@ +#!/usr/bin/env nim + +task build, "builds an example": + setCommand "js" + #os.copyfile("") diff --git a/nim/build.sh b/nim/build.sh new file mode 100755 index 0000000..84abd31 --- /dev/null +++ b/nim/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +nim js jpacrepo.nim +cp nimcache/jpacrepo.js static \ No newline at end of file diff --git a/nim/htmlutils b/nim/htmlutils new file mode 160000 index 0000000..3736513 --- /dev/null +++ b/nim/htmlutils @@ -0,0 +1 @@ +Subproject commit 37365137d3cc3ae7fd54b2f480b49ed4cbe73a39 diff --git a/nim/jpacrepo.nim b/nim/jpacrepo.nim new file mode 100644 index 0000000..cd2c765 --- /dev/null +++ b/nim/jpacrepo.nim @@ -0,0 +1,269 @@ +import dom +import htmlutils.utils +import json +import streams +import tables +import strutils +import sets +from sequtils import map, apply + +var pkgMap : OrderedTable[string, JsonNode] + +type DownloadPanel = ref object + badge : Node + listgroup : Node + pkgs : HashSet[string] + body : Node + +proc newDownloadPanel(parent : Node) : DownloadPanel = + let dp = DownloadPanel() + dp.pkgs = initSet[string]() + htmlDocument(document, parent): + "div": + class ["panel", "panel-default"] + "div": + class ["panel-heading"] + "h3": + class ["panel-title"] + "a": + #attrs {"data-toggle": "collapse", "href": "#dlist"} + text "Package list " + "span": + class ["badge", "pull-right"] + style {"display" :"none"} + cb: + dp.badge = node + + "ul": + class ["list-group"] + cb: + dp.listgroup = node + "div": + class ["panel-body"] + style { + "text-align" : "right", + "display" :"none" + } + "button": + class ["btn", "btn-primary"] + attrs {"type" : "button"} + "span": + class ["glyphicon", "glyphicon-download"] + cb: + node.appendChild(document.createTextNode(" Download")) + cb: + dp.body = node + dp + + +proc updateBadge(dp : DownloadPanel) = + let st = if dp.pkgs.len() > 0: "block" else: "none" + dp.body.style.display = st + dp.badge.style.display = st + dp.badge.textContent = $dp.pkgs.len() + +proc addPkg(dp : DownloadPanel, pkgfile : string) = + if not dp.pkgs.contains(pkgfile): + dp.pkgs.incl(pkgfile) + htmlDocument(document, dp.listgroup): + "li": + var listElement : Node + class ["list-group-item"] + text pkgfile + "span": + class ["glyphicon", "glyphicon-remove", "pull-right"] + cb: + let fn = proc(e : Event) = + dp.pkgs.excl(pkgfile) + listElement.remove() + dp.updateBadge + node.addEventListener("click", fn) + cb: + listElement = node + + dp.updateBadge + +proc createDropdown(parent : Node, data :seq[string], onchange : proc(value : string)) = + htmlDocument(document, parent): + "div": + var button : Node + class ["dropdown"] + "button": + class ["btn", "btn-default", "dropdown-toggle"] + attrs {"data-toggle": "dropdown", "type": "button"} + cb: + for line in data: + node.textContent = line + break + button = node + "ul": + class ["dropdown-menu"] + cb: + for line in data: + htmlDocument(document, node): + "li": + "a": + text line + cb: + let fn = proc(e: Event) = + button.textContent = node.textContent + onchange($node.textContent) + node.addEventListener("click", fn) + +type PkgTable = ref object + addButton : Node + +proc newPkgTable(parent: Node, searchString : string) : PkgTable = + var pkgtable = PkgTable() + var fragments = newSeq[string]() + for fragment in searchString.splitWhitespace(): + fragments.add(fragment) + var searchResult = newOrderedTable[string,JsonNode]() + for key, value in pkgMap: + for fragment in fragments: + if fragment in key: + searchResult.add(key,value) + for table in document.querySelectorAll("table.pkgtable"): + table.parentNode.removeChild(table) + htmlDocument(document, parent): + "table": + class ["table", "table-striped","pkgtable"] + "thead": + "tr": + "th": + "button": + class ["btn", "btn-default"] + attrs {"type": "button"} + "span": + class ["glyphicon", "glyphicon-plus"] + cb: + let txt = document.createTextNode(" Add") + node.appendChild(txt) + pkgtable.addButton = node + "th": + text "Name" + "th": + text "Version" + "th": + text "Arch" + "tbody": + cb: + var i = 0 + for name, versions in searchResult: + closureScope: + htmlDocument(document, node): + "tr": + var archCell : Node + "td": + "div": + class ["checkbox"] + "label": + "input": + attrs {"type" : "checkbox"} + "td": + text $name + "td": + cb: + var data = newSeq[string]() + for version, arches in versions: + data.add(version) + let vs = versions + let change_callback = proc(newValue : string) = + archCell.removeChildren() + var newdata = newSeq[string]() + for arch, pkgname in vs[newValue]: + newdata.add(arch) + createDropdown(archCell, newdata, proc(s : string) = discard) + createDropdown(node, data, change_callback) + "td": + cb: + archCell = node + var data = newSeq[string]() + var arches : JsonNode + for v, a in versions: + arches = a + for arch, pkgname in arches: + data.add(arch) + createDropdown(node, data, proc(s : string) = discard) + pkgtable + +var dp : DownloadPanel +# var pkgTable : PkgTable +htmlDocument document, document.body: + # "img": + # attrs {"src" : "img/background.bpg"} + # style { + # "width" : "100%", + # "position" : "fixed", + # "bottom" : "0", + # "left" : "0", + # "z-index" : "-10" + # } + "div": + var table : Node + style {"background-color" : "rgba(255, 255, 255, 0.25)"} + class ["container"] + "div": + style { + "margin-top" : "20px", + "background-color" : "rgba(224, 224, 224, 0.5)" + } + class ["jumbotron"] + "h1": + text "Jpacrepo" + "p": + text "Personal archlinux package repository" + "div": + "form": + class ["form-horizontal"] + "div": + class ["form-group"] + "label": + class ["control-label", "col-sm-2"] + text " Search package" + "div": + class ["col-sm-10"] + "input": + class ["form-control"] + attrs {"type" : "text"} + cb : + proc add2DownloadList(e : Event) = + let rows = table.querySelectorAll("tbody tr") + for row in rows: + let cbox = row.querySelector("td:first-child input") + if cbox.checked: + let pkgname = $row.querySelector("td:nth-child(2)").textContent + let version = $row.querySelector("td:nth-child(3) button").textContent + let arch = $row.querySelector("td:nth-child(4) button").textContent + dp.addPkg(pkgMap[pkgname][version][arch].getStr) + proc oninput(e : Event) = + if pkgMap.len == 0 or node.value.len < 2: return + let pkgtable = newPkgTable(table, $node.value) + pkgtable.addButton.addEventListener("click", add2DownloadList) + node.addEventListener("input", oninput) + "div": + class ["row"] + "div": + class ["col-sm-3"] + cb: + dp = newDownloadPanel(node) + "div": + class ["col-sm-9"] + cb: + table = node + + + + +let r = newXMLHTTPRequest() +let load_cb = proc(e : Event) = + let response = parseJson($r.responseText) + pkgMap = response.getFields() + # var keys = newSeq[string]() + # for key in response.getFields().keys(): + # keys.add(key) + # echo keys +r.addEventListener("load", load_cb) +r.open("get", "http://oggio88.soon.it/jpacrepo/rest/pkg/map") +r.setRequestHeader("Accept", "application/json") +r.send() diff --git a/nim/static/index.html b/nim/static/index.html new file mode 100644 index 0000000..c68ce6f --- /dev/null +++ b/nim/static/index.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/main/java/com/oggio88/jpacrepo/annotation/CORSManaged.java b/src/main/java/com/oggio88/jpacrepo/annotation/CORSManaged.java new file mode 100644 index 0000000..1802f05 --- /dev/null +++ b/src/main/java/com/oggio88/jpacrepo/annotation/CORSManaged.java @@ -0,0 +1,11 @@ +package com.oggio88.jpacrepo.annotation; + +import javax.ws.rs.NameBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@NameBinding +@Retention(RetentionPolicy.RUNTIME) +public @interface CORSManaged +{ +} \ No newline at end of file diff --git a/src/main/java/com/oggio88/jpacrepo/context/ApplicationContext.java b/src/main/java/com/oggio88/jpacrepo/context/ApplicationContext.java index 702c158..578237c 100644 --- a/src/main/java/com/oggio88/jpacrepo/context/ApplicationContext.java +++ b/src/main/java/com/oggio88/jpacrepo/context/ApplicationContext.java @@ -33,6 +33,8 @@ public class ApplicationContext public boolean invalidateCache = true; + public boolean cors_enabled; + public ApplicationContext(String propertyFile) { systemProperties = new Properties(); @@ -65,6 +67,10 @@ public class ApplicationContext } } } + + String prop = System.getProperty("org.oggio88.jpacrepo.cors"); + if (prop != null) + cors_enabled = Boolean.parseBoolean(prop); } public Properties getSystemProperties() diff --git a/src/main/java/com/oggio88/jpacrepo/service/ApplicationConfig.java b/src/main/java/com/oggio88/jpacrepo/service/ApplicationConfig.java index a5c9998..abc7646 100644 --- a/src/main/java/com/oggio88/jpacrepo/service/ApplicationConfig.java +++ b/src/main/java/com/oggio88/jpacrepo/service/ApplicationConfig.java @@ -24,6 +24,7 @@ public class ApplicationConfig extends Application { HashSet> c = new HashSet<>(); c.add(PacmanWebService.class); + c.add(CORSFilter.class); classes = Collections.unmodifiableSet(c); } diff --git a/src/main/java/com/oggio88/jpacrepo/service/CORSFilter.java b/src/main/java/com/oggio88/jpacrepo/service/CORSFilter.java new file mode 100644 index 0000000..c591d77 --- /dev/null +++ b/src/main/java/com/oggio88/jpacrepo/service/CORSFilter.java @@ -0,0 +1,35 @@ +package com.oggio88.jpacrepo.service; + +import com.oggio88.jpacrepo.annotation.CORSManaged; +import com.oggio88.jpacrepo.context.ApplicationContext; +import com.oggio88.jpacrepo.context.DefaultConfiguration; + +import javax.inject.Inject; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.util.logging.Logger; + +@Provider +@CORSManaged +public class CORSFilter implements ContainerResponseFilter +{ + private final Logger log = Logger.getLogger(CORSFilter.class.getName()); + + @Inject + @DefaultConfiguration + ApplicationContext ctx; + + @Override + public void filter(ContainerRequestContext containerRequestContext, + ContainerResponseContext containerResponseContext) throws IOException + { + if (!ctx.cors_enabled) + { + containerResponseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + } + } +} + diff --git a/src/main/java/com/oggio88/jpacrepo/service/PacmanWebService.java b/src/main/java/com/oggio88/jpacrepo/service/PacmanWebService.java index 0ef5278..be1f773 100644 --- a/src/main/java/com/oggio88/jpacrepo/service/PacmanWebService.java +++ b/src/main/java/com/oggio88/jpacrepo/service/PacmanWebService.java @@ -1,5 +1,6 @@ package com.oggio88.jpacrepo.service; +import com.oggio88.jpacrepo.annotation.CORSManaged; import com.oggio88.jpacrepo.context.ApplicationContext; import com.oggio88.jpacrepo.context.DefaultConfiguration; import com.oggio88.jpacrepo.model.PkgData; @@ -8,6 +9,7 @@ import com.oggio88.jpacrepo.model.PkgName; import com.oggio88.jpacrepo.model.StringList; import com.oggio88.jpacrepo.pacbase.Parser; import com.oggio88.jpacrepo.persistence.QueryEngine; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import javax.annotation.Resource; import javax.ejb.*; @@ -30,6 +32,7 @@ import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; +@CORSManaged @Singleton @Path("/pkg") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @@ -59,35 +62,6 @@ public class PacmanWebService @DefaultConfiguration private ApplicationContext ctx; - @GET - @Path("search") - public Response getPackage(@QueryParam("name") String name, - @QueryParam("version") String version, - @QueryParam("arch") String arch, - @QueryParam("filename") String filename, - @QueryParam("md5sum") String md5sum) - { - if (md5sum != null) - { - return getPackageByHash(md5sum); - } - else if (filename != null) - { - return getPackageByFileName(filename); - } - else - { - - QueryEngine qe = new QueryEngine(PkgData.class); - qe.select(); - if (name != null) qe.where("name", "=", name); - if (version != null) qe.where("version", "=", version); - if (arch != null) qe.where("arch", "=", arch); - String query = qe.build(); - return manageQueryResult(em.createQuery(query).getResultList()); - } - } - @GET @Path("searchByName/{name}") public Response searchByName(@PathParam("name") String name) @@ -139,6 +113,7 @@ public class PacmanWebService } @GET + @Produces(MediaType.APPLICATION_JSON) @Path("map") public Response getPackageMap() { @@ -317,6 +292,7 @@ public class PacmanWebService } @GET + @CORSManaged @Path("/search") public List searchPackage( @QueryParam("name") String name, @@ -377,6 +353,12 @@ public class PacmanWebService return query.getResultList(); } + @POST + public Response foo() + { + TarArchiveouputStream tais = new TarArchiveOutputStream(); + } + private Response manageQueryResult(List list) { return manageQueryResult(list, false);