Permalink
Browse files

Merge pull request #41 from ielab/chain-plugin

Chain plugin
  • Loading branch information...
hscells committed Oct 5, 2018
2 parents 43bf800 + b685348 commit a517c3efc50d699137c2d96cf88ea03ee496356f
View
@@ -11,7 +11,7 @@ searchrefiner: $(go_source) plugins
go build -o server cmd/searchrefiner/server.go
plugins: $(go_plugin)
$(foreach dir, $(plugin_files), go build -buildmode=plugin -o $(dir)/plugin.so $(dir)/main.go)
$(foreach dir, $(plugin_files), go build -buildmode=plugin -o $(dir)/plugin.so $(dir)/main.go;)
run: all
@./server
View
2 api.go
@@ -85,7 +85,7 @@ func (s Server) ApiTree(c *gin.Context) {
}
var root combinator.LogicalTree
root, _, err = combinator.NewLogicalTree(groove.NewPipelineQuery("searchrefiner", "0", repr.(cqr.CommonQueryRepresentation)), s.Entrez, seen)
root, _, err = combinator.NewLogicalTree(groove.NewPipelineQuery("searchrefiner", "0", repr.(cqr.CommonQueryRepresentation)), s.Entrez, QueryCacher)
if err != nil {
c.AbortWithError(500, err)
return
View
@@ -122,6 +122,10 @@ func (s Server) ApiAccountLogout(c *gin.Context) {
func (s Server) ApiAccountUsername(c *gin.Context) {
username := s.UserState.Username(c.Request)
if !s.UserState.IsLoggedIn(username) {
c.String(http.StatusOK, "anonymous")
return
}
c.String(http.StatusOK, username)
return
}
View
@@ -0,0 +1,228 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"github.com/hscells/groove/stats"
"github.com/ielab/searchrefiner"
"github.com/xyproto/permissionbolt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"plugin"
"strings"
)
func main() {
f, err := os.Open("config.json")
if err != nil {
log.Fatalln(err)
}
var c searchrefiner.Config
err = json.NewDecoder(f).Decode(&c)
if err != nil {
log.Fatalln(err)
}
lf, err := os.OpenFile("web/static/log", os.O_WRONLY|os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatalln(err)
}
lf.Truncate(0)
dbPath := "citemed.db"
g := gin.Default()
perm, err := permissionbolt.NewWithConf(dbPath)
if err != nil {
log.Fatalln(err)
}
perm.Clear()
perm.AddUserPath("/tree")
perm.AddUserPath("/query")
perm.AddUserPath("/transform")
perm.AddUserPath("/settings")
perm.AddUserPath("/api")
perm.AddUserPath("/plugins")
perm.AddPublicPath("/account")
perm.AddPublicPath("/static")
perm.AddPublicPath("/help")
perm.AddPublicPath("/error")
perm.AddPublicPath("/api/username")
perm.AddAdminPath("/admin")
ss, err := stats.NewEntrezStatisticsSource(
stats.EntrezOptions(stats.SearchOptions{Size: 100000, RunName: "searchrefiner"}),
stats.EntrezTool("searchrefiner"),
stats.EntrezEmail(c.Entrez.Email),
stats.EntrezAPIKey(c.Entrez.APIKey))
if err != nil {
log.Fatalln(err)
}
s := searchrefiner.Server{
UserState: perm.UserState(),
Perm: perm,
Config: c,
Queries: make(map[string][]searchrefiner.Query),
Settings: make(map[string]searchrefiner.Settings),
Entrez: ss,
}
permissionHandler := func(c *gin.Context) {
if perm.Rejected(c.Writer, c.Request) {
c.HTML(500, "error.html", searchrefiner.ErrorPage{Error: "unauthorised user", BackLink: "/"})
c.AbortWithStatus(http.StatusForbidden)
return
} else if len(perm.UserState().Username(c.Request)) > 0 && !perm.UserState().IsConfirmed(perm.UserState().Username(c.Request)) {
if !strings.HasPrefix(c.Request.URL.Path, "/account") && !strings.HasPrefix(c.Request.URL.Path, "/static") {
c.Data(http.StatusForbidden, "text/plain", []byte(fmt.Sprintf("Your account is waiting to be confirmed, please email %v if this takes longer than 24 hours.", s.Config.AdminEmail)))
c.AbortWithStatus(http.StatusForbidden)
return
}
}
c.Next()
}
g.Use(permissionHandler)
g.Use(gzip.Gzip(gzip.BestCompression))
g.LoadHTMLFiles(append([]string{
// Views.
"web/query.html", "web/index.html", "web/transform.html", "web/tree.html",
"web/account_create.html", "web/account_login.html", "web/admin.html",
"web/help.html", "web/error.html", "web/results.html", "web/settings.html", "web/plugins.html",
}, searchrefiner.Components...)...)
g.Static("/static/", "./web/static")
// Handle plugins.
files, err := ioutil.ReadDir("plugin")
if err != nil {
log.Fatalln(err)
}
for _, file := range files {
if file.IsDir() {
// Open the shared object file that will become the plugin.
p := file.Name()
plug, err := plugin.Open(path.Join("plugin", p, "plugin.so"))
if err != nil {
panic(err)
}
// Grab the exported type.
sym, err := plug.Lookup(strings.Title(p))
if err != nil {
log.Fatalln(err)
}
// Ensure the type implements the plugin.
var handle searchrefiner.Plugin
var ok bool
if handle, ok = sym.(searchrefiner.Plugin); !ok {
log.Fatalln("could not cast", p, "to plugin")
}
// Configure the permissions for this plugin.
p = path.Join("/plugin/", p)
switch handle.PermissionType() {
case searchrefiner.PluginAdmin:
perm.AddAdminPath(p)
case searchrefiner.PluginPublic:
perm.AddPublicPath(p)
case searchrefiner.PluginUser:
perm.AddUserPath(p)
default:
perm.AddPublicPath(p)
}
s.Plugins = append(s.Plugins, searchrefiner.InternalPluginDetails{
URL: p,
PluginDetails: handle.Details(),
})
// Register the handler with gin.
g.GET(p, func(c *gin.Context) {
handle.Serve(s, c)
})
g.POST(p, func(c *gin.Context) {
handle.Serve(s, c)
})
}
}
// Administration.
g.GET("/admin", s.HandleAdmin)
g.POST("/admin/api/confirm", s.ApiAdminConfirm)
// Authentication views.
g.GET("/account/login", searchrefiner.HandleAccountLogin)
g.GET("/account/create", searchrefiner.HandleAccountCreate)
// Authentication API.
g.POST("/account/api/login", s.ApiAccountLogin)
g.POST("/account/api/create", s.ApiAccountCreate)
g.GET("/account/api/logout", s.ApiAccountLogout)
g.GET("/api/username", s.ApiAccountUsername)
// Main query interface.
g.GET("/", s.HandleIndex)
g.GET("/clear", s.HandleClear)
g.POST("/query", s.HandleQuery)
g.GET("/query", s.HandleQuery)
g.POST("/results", s.HandleResults)
g.GET("/results", s.HandleResults)
g.POST("/api/scroll", s.ApiScroll)
// Editor interface.
g.GET("/transform", searchrefiner.HandleTransform)
g.POST("/transform", searchrefiner.HandleTransform)
g.POST("/api/transform", searchrefiner.ApiTransform)
g.POST("/api/cqr2query", searchrefiner.ApiCQR2Query)
g.POST("/api/query2cqr", searchrefiner.ApiQuery2CQR)
// Visualisation interface.
g.GET("/tree", searchrefiner.HandleTree)
g.POST("/tree", searchrefiner.HandleTree)
g.POST("/api/tree", s.ApiTree)
// Settings page.
g.GET("/settings", s.HandleSettings)
g.POST("/api/settings/relevant", s.ApiSettingsRelevantSet)
// Plugins page.
g.GET("/plugins", s.HandlePlugins)
// Other utility pages.
g.GET("/help", func(c *gin.Context) {
c.HTML(http.StatusOK, "help.html", nil)
})
mw := io.MultiWriter(lf, os.Stdout)
log.SetOutput(mw)
// Set a global server configuration variable so plugins have access to it.
searchrefiner.ServerConfiguration = s
fmt.Print(`
_ ___ _
___ ___ ___ ___ ___| |_ ___ ___| _|_|___ ___ ___
|_ -| -_| .'| _| _| | _| -_| _| | | -_| _|
|___|___|__,|_| |___|_|_|_| |___|_| |_|_|_|___|_|
Harry Scells 2018
harrisen.scells@hdr.qut.edu.au
https://ielab.io/searchrefiner
`)
g.Run(c.Host)
}
@@ -4,10 +4,11 @@
let bb;
let request = new XMLHttpRequest();
request.addEventListener("load", function (ev) {
let events = ["click", "dblclick", "submit", "cut", "copy", "paste", "select"];
if (ev.currentTarget.status !== 200) {
bb = BigBro.init("anonymous", window.location.hostname + ":1984");
bb = BigBro.init("anonymous", "43.240.96.223:1984", events);
} else {
bb = BigBro.init(ev.target.responseText, window.location.hostname + ":1984");
bb = BigBro.init(ev.target.responseText, "43.240.96.223:1984", events);
}
});
request.open("GET", "/api/username", true);
@@ -8,6 +8,8 @@
<link rel="stylesheet" href="/static/spectre.min.css" type="text/css">
<link rel="stylesheet" href="/static/spectre-icons.min.css" type="text/css">
<link rel="stylesheet" href="/static/spectre-exp.min.css" type="text/css">
<link rel="stylesheet" href="/static/searchrefiner.css" type="text/css">
<link rel="stylesheet" href="/static/account.css" type="text/css">
</head>
<body>
<div class="container" style="padding-top: 15vh">
@@ -16,7 +18,7 @@
<figure class="avatar avatar-lg">
<img src="/static/favicon.png">
</figure>
<div class="h5 mt-2">searchrefiner</div>
<div class="h2 mt-2">searchrefiner</div>
</div>
{{end}}
@@ -7,7 +7,7 @@
<ul class="menu off-canvas-sidebar text-center" id="sidebar">
<li class="menu-item">
<div class="tile tile-centered">
<div class="tile-icon"><img src="static/favicon.png" class="avatar" width="32px"></div>
<div class="tile-icon"><img src="/static/favicon.png" class="avatar" width="32px"></div>
<div class="tile-content">searchrefiner</div>
</div>
</li>
@@ -16,6 +16,7 @@
<li class="menu-item"><a href="/tree">QueryVis</a></li>
<li class="menu-item"><a href="/transform">Transform</a></li>
<li class="menu-item"><a href="/settings">Settings</a></li>
<li class="menu-item"><a href="/plugins">Plugins</a></li>
</ul>
<a class="off-canvas-overlay" href="#close"></a>
</div>
@@ -1,5 +1,5 @@
{{define "version"}}
04.Sep.2018
20.Sep.2018
{{end}}
{{define "footer"}}
View
@@ -11,7 +11,7 @@ import (
)
var (
seen = combinator.NewFileQueryCache("file_cache")
QueryCacher = combinator.NewFileQueryCache("file_cache")
Components = []string{"components/sidebar.tmpl.html", "components/util.tmpl.html", "components/login.template.html", "components/bigbro.tmpl.html"}
ServerConfiguration = Server{}
)
@@ -61,22 +61,43 @@ type Server struct {
Settings map[string]Settings
Config Config
Entrez stats.EntrezStatisticsSource
Plugins []InternalPluginDetails
}
// Plugin is the interface that must be implemented in order to register an external tool.
// See more: http://ielab.io/searchrefiner/plugins/
type Plugin interface {
Serve(Server, *gin.Context)
PermissionType() PluginPermission
Details() PluginDetails
}
// PluginDetails are details about a plugin which is shown in the plugins page of searchrefiner.
type PluginDetails struct {
Title string
Description string
Author string
Version string
ProjectURL string
}
// InternalPluginDetails contains details about a plugin which are vital in rendering the plugin page.
type InternalPluginDetails struct {
URL string
PluginDetails
}
// TemplatePlugin is the template method which will include searchrefiner components.
func TemplatePlugin(p string) template.Template {
_, f := path.Split(p)
return *template.Must(template.New(f).ParseFiles(append(Components, p)...))
}
// RenderPlugin returns a gin-compatible HTML renderer for plugins.
func RenderPlugin(tmpl template.Template, data interface{}) render.HTML {
return render.HTML{
Template: &tmpl,
Name: tmpl.Name(),
Data: data,
}
}
}
View
@@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.
### Added
+ Integrate the ability for plugins to be developed and loaded into interface.
+ Create a "chain" plugin based off recent query transformation research.
+ Create a "chain" plugin based off recent query transformation research (see [plugins](/plugins)).
### Changed
View
@@ -17,11 +17,11 @@ the interface at this [demo link](http://43.240.96.223:4853/) (note that users m
## Links
- Visit the [demo](http://43.240.96.223:4853/).
- Visit the [demo](http://43.240.96.223/).
- Read how to [set-up](setup.md) a local version.
- Read how to [authenticate](authentication.md) programmatically.
- Read what [APIs](api.md) are exposed.
- Read what [tools](tools.md) are made available in the interface.
- View the source on [GitHub](https://github.com/ielab/searchrefiner).
- Read the [documentation](http://43.240.96.223:4853/help) on using the interface.
- Read the [documentation](http://43.240.96.223/help) on using the interface.
- Contribute to [development](https://github.com/ielab/searchrefiner/issues).
Oops, something went wrong.

0 comments on commit a517c3e

Please sign in to comment.