Start integrating Hugo in the new plugin

This commit is contained in:
Henrique Dias 2017-07-11 16:58:18 +01:00
parent 465b10a02a
commit 8b1d36dfb9
No known key found for this signature in database
GPG Key ID: 936F5EB68D786730
26 changed files with 837 additions and 2497 deletions

View File

@ -27,6 +27,8 @@
if (file.match(/\.(js|css)$/)) { %>
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
<!-- Plugins info -->
<script>{{ range $index, $plugin := .Plugins }}{{ JS $plugin.JavaScript }}{{ end}}</script>
<style>
#loading {
position: fixed;

View File

@ -48,6 +48,7 @@ export default {
lineNumbers: (this.req.language !== 'markdown'),
viewportMargin: Infinity,
autofocus: true,
mode: this.req.language,
theme: (this.req.language === 'markdown') ? 'markdown' : 'ttcn',
lineWrapping: (this.req.language === 'markdown')
})
@ -66,7 +67,8 @@ export default {
value: this.req.metadata,
viewportMargin: Infinity,
lineWrapping: true,
theme: 'markdown'
theme: 'markdown',
mode: this.metalang
})
CodeMirror.autoLoadMode(this.metadata, this.metalang)

View File

@ -16,6 +16,20 @@
<i class="material-icons" title="Save">save</i>
</button>
<div v-for="plugin in plugins" :key="plugin.name">
<button class="action"
v-for="action in plugin.header.visible"
v-if="action.if(pluginData, $route)"
@click="action.click($event, pluginData, $route)"
:aria-label="action.name"
:id="action.id"
:title="action.name"
:key="action.name">
<i class="material-icons">{{ action.icon }}</i>
<span>{{ action.name }}</span>
</button>
</div>
<button @click="openMore" id="more" aria-label="More" title="More" class="action">
<i class="material-icons">more_vert</i>
</button>
@ -36,6 +50,20 @@
<delete-button v-show="showDeleteButton"></delete-button>
</div>
<div v-for="plugin in plugins" :key="plugin.name">
<button class="action"
v-for="action in plugin.header.hidden"
v-if="action.if(pluginData, $route)"
@click="action.click($event, pluginData, $route)"
:id="action.id"
:aria-label="action.name"
:title="action.name"
:key="action.name">
<i class="material-icons">{{ action.icon }}</i>
<span>{{ action.name }}</span>
</button>
</div>
<switch-button v-show="showSwitchButton"></switch-button>
<download-button v-show="showCommonButton"></download-button>
<upload-button v-show="showUpload"></upload-button>
@ -61,6 +89,8 @@ import DownloadButton from './buttons/Download'
import SwitchButton from './buttons/SwitchView'
import MoveButton from './buttons/Move'
import {mapGetters, mapState} from 'vuex'
import api from '@/utils/api'
import buttons from '@/utils/buttons'
export default {
name: 'main',
@ -76,7 +106,13 @@ export default {
},
data: function () {
return {
width: window.innerWidth
width: window.innerWidth,
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
created () {
@ -93,7 +129,8 @@ export default {
'user',
'loading',
'reload',
'multiple'
'multiple',
'plugins'
]),
isMobile () {
return this.width <= 736

View File

@ -17,8 +17,8 @@
</button>
</div>
<div v-for="plugin in plugins">
<button v-for="action in plugin.sidebar" @click="action.click" :aria-label="action.name" :title="action.name" :key="action.name" class="action">
<div v-for="plugin in plugins" :key="plugin.name">
<button v-for="action in plugin.sidebar" @click="action.click($event, pluginData, $route)" :aria-label="action.name" :title="action.name" :key="action.name" class="action">
<i class="material-icons">{{ action.icon }}</i>
<span>{{ action.name }}</span>
</button>
@ -36,32 +36,38 @@
</button>
</div>
<p class="credits">Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.<br><a @click="help">Help</a></p>
<p class="credits">
<span>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</span>
<span v-for="plugin in plugins" :key="plugin.name" v-html="plugin.credits"><br></span>
<span><a @click="help">Help</a></span>
</p>
</nav>
</template>
<script>
import {mapState} from 'vuex'
import auth from '@/utils/auth'
import buttons from '@/utils/buttons'
import api from '@/utils/api'
export default {
name: 'sidebar',
data: () => {
data: function () {
return {
plugins: []
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
computed: {
...mapState(['user']),
...mapState(['user', 'plugins']),
active () {
return this.$store.state.show === 'sidebar'
}
},
mounted () {
if (window.plugins !== undefined || window.plugins !== null) {
this.plugins = window.plugins
}
},
methods: {
help: function () {
this.$store.commit('showHover', 'help')

View File

@ -11,6 +11,26 @@
<error v-else-if="showError"></error>
<success v-else-if="showSuccess"></success>
<template v-for="plugin in plugins">
<form class="prompt"
v-for="prompt in plugin.prompts"
:key="prompt.name"
v-if="show === prompt.name"
@submit="prompt.submit($event, pluginData, $route)">
<h3>{{ prompt.title }}</h3>
<p>{{ prompt.description }}</p>
<input v-for="input in prompt.inputs"
:key="input.name"
:type="input.type"
:name="input.name"
:placeholder="input.placeholder">
<div>
<input type="submit" class="ok" :value="prompt.ok">
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
</div>
</form>
</template>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
@ -27,6 +47,8 @@ import Success from './Success'
import NewFile from './NewFile'
import NewDir from './NewDir'
import { mapState } from 'vuex'
import buttons from '@/utils/buttons'
import api from '@/utils/api'
export default {
name: 'prompts',
@ -42,8 +64,18 @@ export default {
NewDir,
Help
},
data: function () {
return {
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
computed: {
...mapState(['show']),
...mapState(['show', 'plugins']),
showError: function () { return this.show === 'error' },
showSuccess: function () { return this.show === 'success' },
showInfo: function () { return this.show === 'info' },

View File

@ -29,6 +29,7 @@
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transform: scale(0);
transition: .1s ease-in-out transform;
transform-origin: top right;
z-index: 99999;
}
#dropdown > div {

View File

@ -37,11 +37,12 @@
margin: .5em 0 1em;
}
.prompt input {
.prompt input:not([type="submit"]) {
width: 100%;
border: 1px solid #dadada;
line-height: 1;
padding: .3em;
margin: .3em 0;
}
.prompt code {

View File

@ -175,6 +175,11 @@
color: #a5a5a5;
}
.credits span {
display: block;
margin: .3em 0;
}
.credits a,
.credits a:hover {
color: inherit;

View File

@ -8,6 +8,7 @@ Vue.use(Vuex)
const state = {
user: {},
req: {},
plugins: window.plugins || [],
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
jwt: '',
loading: false,

View File

@ -375,5 +375,6 @@ export default {
updatePassword,
updateCSS,
getCommands,
updateCommands
updateCommands,
removePrefix
}

24
auth.go
View File

@ -13,7 +13,7 @@ import (
)
// authHandler proccesses the authentication for the user.
func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func authHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Receive the credentials from the request and unmarshal them.
var cred User
if r.Body == nil {
@ -26,7 +26,7 @@ func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int
}
// Checks if the user exists.
u, ok := c.fm.Users[cred.Username]
u, ok := c.FM.Users[cred.Username]
if !ok {
return http.StatusForbidden, nil
}
@ -36,19 +36,19 @@ func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int
return http.StatusForbidden, nil
}
c.us = u
c.User = u
return printToken(c, w)
}
// renewAuthHandler is used when the front-end already has a JWT token
// and is checking if it is up to date. If so, updates its info.
func renewAuthHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func renewAuthHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
ok, u := validateAuth(c, r)
if !ok {
return http.StatusForbidden, nil
}
c.us = u
c.User = u
return printToken(c, w)
}
@ -59,11 +59,11 @@ type claims struct {
}
// printToken prints the final JWT token to the user.
func printToken(c *requestContext, w http.ResponseWriter) (int, error) {
func printToken(c *RequestContext, w http.ResponseWriter) (int, error) {
// Creates a copy of the user and removes it password
// hash so it never arrives to the user.
u := User{}
u = *c.us
u = *c.User
u.Password = ""
// Builds the claims.
@ -77,7 +77,7 @@ func printToken(c *requestContext, w http.ResponseWriter) (int, error) {
// Creates the token and signs it.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
string, err := token.SignedString(c.fm.key)
string, err := token.SignedString(c.FM.key)
if err != nil {
return http.StatusInternalServerError, err
@ -106,9 +106,9 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
// validateAuth is used to validate the authentication and returns the
// User if it is valid.
func validateAuth(c *requestContext, r *http.Request) (bool, *User) {
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
return c.fm.key, nil
return c.FM.key, nil
}
var claims claims
token, err := request.ParseFromRequestWithClaims(r,
@ -121,12 +121,12 @@ func validateAuth(c *requestContext, r *http.Request) (bool, *User) {
return false, nil
}
u, ok := c.fm.Users[claims.User.Username]
u, ok := c.FM.Users[claims.User.Username]
if !ok {
return false, nil
}
c.us = u
c.User = u
return true, u
}

View File

@ -1 +1,172 @@
package hugo
import (
"errors"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
rice "github.com/GeertJohan/go.rice"
"github.com/hacdias/filemanager"
"github.com/hacdias/filemanager/variables"
"github.com/robfig/cron"
)
type hugo struct {
// Website root
Root string
// Public folder
Public string
// Hugo executable path
Exe string
// Hugo arguments
Args []string
// Indicates if we should clean public before a new publish.
CleanPublic bool
// A map of events to a slice of commands.
Commands map[string][]string
// AllowPublish
javascript string
}
func (h hugo) BeforeAPI(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// If we are using the 'magic url' for the settings, we should redirect the
// request for the acutual path.
if r.URL.Path == "/settings/" || r.URL.Path == "/settings" {
var frontmatter string
var err error
if _, err = os.Stat(filepath.Join(h.Root, "config.yaml")); err == nil {
frontmatter = "yaml"
}
if _, err = os.Stat(filepath.Join(h.Root, "config.json")); err == nil {
frontmatter = "json"
}
if _, err = os.Stat(filepath.Join(h.Root, "config.toml")); err == nil {
frontmatter = "toml"
}
r.URL.Path = "/config." + frontmatter
return 0, nil
}
// From here on, we only care about 'hugo' router so we can bypass
// the others.
if c.Router != "hugo" {
return 0, nil
}
if r.Method != http.MethodPost {
return http.StatusMethodNotAllowed, nil
}
if r.Header.Get("Archetype") != "" {
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
filename = filepath.Clean(filename)
filename = strings.TrimPrefix(filename, "/")
archetype := r.Header.Get("archetype")
if !strings.HasSuffix(filename, ".md") && !strings.HasSuffix(filename, ".markdown") {
return http.StatusBadRequest, errors.New("Your file must be markdown")
}
args := []string{"new", filename, "--kind", archetype}
if err := Run(h.Exe, args, h.Root); err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("Location", "/files/content/"+filename)
return http.StatusCreated, nil
}
if r.Header.Get("Regenerate") == "true" {
// Before save command handler.
path := filepath.Clean(filepath.Join(string(c.User.FileSystem), r.URL.Path))
if err := c.FM.Runner("before_publish", path); err != nil {
return http.StatusInternalServerError, err
}
args := []string{"undraft", path}
if err := Run(h.Exe, args, h.Root); err != nil {
return http.StatusInternalServerError, err
}
h.run(false)
if err := c.FM.Runner("before_publish", path); err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
if r.Header.Get("Schedule") != "" {
return h.schedule(c, w, r)
}
return http.StatusNotFound, nil
}
func (h hugo) AfterAPI(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
return 0, nil
}
func (h hugo) JavaScript() string {
return rice.MustFindBox("./").MustString("hugo.js")
}
// run runs Hugo with the define arguments.
func (h hugo) run(force bool) {
// If the CleanPublic option is enabled, clean it.
if h.CleanPublic {
os.RemoveAll(h.Public)
}
// Prevent running if watching is enabled
if b, pos := variables.StringInSlice("--watch", h.Args); b && !force {
if len(h.Args) > pos && h.Args[pos+1] != "false" {
return
}
if len(h.Args) == pos+1 {
return
}
}
if err := Run(h.Exe, h.Args, h.Root); err != nil {
log.Println(err)
}
}
// schedule schedules a post to be published later.
func (h hugo) schedule(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
path = filepath.Clean(path)
if err != nil {
return http.StatusInternalServerError, err
}
scheduler := cron.New()
scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
args := []string{"undraft", path}
if err := Run(h.Exe, args, h.Root); err != nil {
log.Printf(err.Error())
return
}
h.run(false)
})
scheduler.Start()
return http.StatusOK, nil
}

View File

@ -1,42 +1,167 @@
'use strict'
'use strict';
if (window.plugins === undefined || window.plugins === null) {
(function () {
if (window.plugins === undefined || window.plugins === null) {
window.plugins = []
}
}
window.plugins.append({
let regenerate = function (data, url) {
url = data.api.removePrefix(url)
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${data.store.state.baseURL}/api/hugo${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${data.store.state.jwt}`)
request.setRequestHeader('Regenerate', 'true')
request.onload = () => {
if (request.status === 200) {
resolve()
} else {
reject(request.responseText)
}
}
request.onerror = (error) => reject(error)
request.send()
})
}
let newArchetype = function (data, file, type) {
file = data.api.removePrefix(file)
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${data.store.state.baseURL}/api/hugo${file}`, true)
request.setRequestHeader('Authorization', `Bearer ${data.store.state.jwt}`)
request.setRequestHeader('Archetype', encodeURIComponent(type))
request.onload = () => {
if (request.status === 200) {
resolve(request.getResponseHeader('Location'))
} else {
reject(request.responseText)
}
}
request.onerror = (error) => reject(error)
request.send()
})
}
window.plugins.push({
name: 'hugo',
credits: 'With a flavour of <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-hugo">Hugo</a>.',
header: {
visible: [
{
if: function (data, route) {
return (data.store.state.req.kind === 'editor' &&
!data.store.state.loading &&
data.store.state.req.metadata !== undefined &&
data.store.state.req.metadata !== null &&
data.store.state.user.allowEdit)
// TODO: add allowPublish
},
click: function (event, data, route) {
event.preventDefault()
document.getElementById('save-button').click()
// TODO: wait for save to finish?
data.buttons.loading('publish')
regenerate(data, route.path)
.then(() => {
data.buttons.done('publish')
data.store.commit('setReload', true)
})
.catch((error) => {
data.buttons.done('publish')
data.store.commit('showError', error)
})
},
id: 'publish-button',
icon: 'send',
name: 'Publish'
}
],
hidden: [
{
if: function (data, route) {
return (data.store.state.req.kind === 'editor' &&
!data.store.state.loading &&
data.store.state.req.metadata !== undefined &&
data.store.state.req.metadata !== null)
},
click: function (event, data, route) {
console.log('Schedule')
},
id: 'schedule-button',
icon: 'alarm',
name: 'Schedule'
}
]
},
sidebar: [
{
click: function (event) {
console.log('evt')
click: function (event, data, route) {
data.router.push({ path: '/files/settings' })
},
icon: 'settings_applications',
name: 'Settings'
},
{
click: function (event) {
click: function (event, data, route) {
data.store.commit('showHover', 'new-archetype')
},
icon: 'merge_type',
name: 'Hugo new'
},
{
click: function (event, data, route) {
console.log('evt')
},
icon: 'remove_red_eye',
name: 'Preview'
}
],
prompts: [
{
name: 'new-archetype',
title: 'New file',
description: 'Create a new post based on an archetype. Your file will be created on content folder.',
inputs: [
{
type: 'text',
name: 'file',
placeholder: 'File name'
},
{
type: 'text',
name: 'archetype',
placeholder: 'Archetype'
}
],
ok: 'Create',
submit: function (event, data, route) {
event.preventDefault()
console.log(event)
let file = event.currentTarget.querySelector('[name="file"]').value
let type = event.currentTarget.querySelector('[name="archetype"]').value
if (type === '') type = 'default'
data.store.commit('closeHovers')
newArchetype(data, '/' + file, type)
.then((url) => {
data.router.push({ path: url })
})
.catch(error => {
data.store.commit('showError', error)
})
}
}
]
})
/*
{{ define "sidebar-addon" }}
<a class="action" href="{{ .BaseURL }}/content/">
<i class="material-icons">subject</i>
<span>Posts and Pages</span>
</a>
<a class="action" href="{{ .BaseURL }}/themes/">
<i class="material-icons">format_paint</i>
<span>Themes</span>
</a>
<a class="action" href="{{ .BaseURL }}/settings/">
<i class="material-icons">settings</i>
<span>Settings</span>
</a>
{{ end }}
*/
})
})()

26
caddy/hugo/old.js Normal file
View File

@ -0,0 +1,26 @@
hugo.schedule = function (event) {
event.preventDefault();
let date = document.getElementById('date').value;
if(document.getElementById('publishDate')) {
date = document.getElementById('publishDate').value;
}
buttons.setLoading('publish');
let data = JSON.stringify(form2js(document.querySelector('form'))),
headers = {
'Kind': document.getElementById('editor').dataset.kind,
'Schedule': 'true'
};
webdav.put(window.location.pathname, data, headers)
.then(() => {
buttons.setDone('publish');
})
.catch(e => {
console.log(e);
buttons.setDone('publish', false)
})
}

176
caddy/hugo/setup.go Normal file
View File

@ -0,0 +1,176 @@
package hugo
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/hacdias/filemanager"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"golang.org/x/net/webdav"
)
var (
errHugoNotFound = errors.New("It seems that tou don't have 'hugo' on your PATH")
)
// setup configures a new FileManager middleware instance.
func setup(c *caddy.Controller) error {
configs, err := parse(c)
if err != nil {
return err
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return plugin{Configs: configs, Next: next}
})
return nil
}
func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
var (
configs []*filemanager.FileManager
)
for c.Next() {
// hugo [directory] [admin] {
// database path
// }
directory := "."
admin := "/admin"
database := ""
// Get the baseURL and baseScope
args := c.RemainingArgs()
if len(args) == 1 {
directory = args[0]
}
if len(args) > 1 {
admin = args[1]
}
for c.NextBlock() {
switch c.Val() {
case "database":
if !c.NextArg() {
return nil, c.ArgErr()
}
database = c.Val()
}
}
caddyConf := httpserver.GetConfig(c)
path := filepath.Join(caddy.AssetsPath(), "hugo")
err := os.MkdirAll(path, 0700)
if err != nil {
return nil, err
}
// if there is a database path and it is not absolute,
// it will be relative to Caddy folder.
if !filepath.IsAbs(database) && database != "" {
database = filepath.Join(path, database)
}
// If there is no database path on the settings,
// store one in .caddy/hugo/name.db.
if database == "" {
// The name of the database is the hashed value of a string composed
// by the host, address path and the baseurl of this File Manager
// instance.
hasher := md5.New()
hasher.Write([]byte(caddyConf.Addr.Host + caddyConf.Addr.Path + admin))
sha := hex.EncodeToString(hasher.Sum(nil))
database = filepath.Join(path, sha+".db")
fmt.Println("[WARNING] A database is going to be created for your Hugo instace at " + database +
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
}
m, err := filemanager.New(database, filemanager.User{
Username: "admin",
Password: "admin",
AllowCommands: true,
AllowEdit: true,
AllowNew: true,
Commands: []string{"git", "svn", "hg"},
Rules: []*filemanager.Rule{{
Regex: true,
Allow: false,
Regexp: &filemanager.Regexp{Raw: "\\/\\..+"},
}},
CSS: "",
FileSystem: webdav.Dir(directory),
})
if err != nil {
return nil, err
}
// Initialize the default settings for Hugo.
hugo := &hugo{
Root: directory,
Public: filepath.Join(directory, "public"),
Args: []string{},
CleanPublic: true,
Commands: map[string][]string{
"before_publish": []string{},
"after_publish": []string{},
},
}
// Try to find the Hugo executable path.
if hugo.Exe, err = exec.LookPath("hugo"); err != nil {
return nil, errHugoNotFound
}
err = m.RegisterPlugin("hugo", hugo)
if err != nil {
return nil, err
}
m.SetBaseURL(admin)
m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/"))
configs = append(configs, m)
}
return configs, nil
}
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (p plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
for i := range p.Configs {
// Checks if this Path should be handled by File Manager.
if !httpserver.Path(r.URL.Path).Matches(p.Configs[i].BaseURL) {
continue
}
return p.Configs[i].ServeHTTP(w, r)
}
return p.Next.ServeHTTP(w, r)
}
func init() {
caddy.RegisterPlugin("hugo", caddy.Plugin{
ServerType: "http",
Action: setup,
})
}
type plugin struct {
Next httpserver.Handler
Configs []*filemanager.FileManager
}

15
caddy/hugo/utils.go Normal file
View File

@ -0,0 +1,15 @@
package hugo
import (
"os"
"os/exec"
)
// Run executes an external command
func Run(command string, args []string, path string) error {
cmd := exec.Command(command, args...)
cmd.Dir = path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

View File

@ -22,7 +22,7 @@ var (
)
// command handles the requests for VCS related commands: git, svn and mercurial
func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func command(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Upgrades the connection to a websocket and checks for errors.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
@ -51,7 +51,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
// Check if the command is allowed
allowed := false
for _, cmd := range c.us.Commands {
for _, cmd := range c.User.Commands {
if cmd == command[0] {
allowed = true
}
@ -77,7 +77,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
}
// Gets the path and initializes a buffer.
path := string(c.us.FileSystem) + "/" + r.URL.Path
path := string(c.User.FileSystem) + "/" + r.URL.Path
path = filepath.Clean(path)
buff := new(bytes.Buffer)

View File

@ -14,17 +14,17 @@ import (
// downloadHandler creates an archive in one of the supported formats (zip, tar,
// tar.gz or tar.bz2) and sends it to be downloaded.
func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
query := r.URL.Query().Get("format")
if !c.fi.IsDir {
if !c.FI.IsDir {
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
} else {
w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
w.Header().Set("Content-Disposition", "attachment; filename="+c.FI.Name)
}
http.ServeFile(w, r, c.fi.Path)
http.ServeFile(w, r, c.FI.Path)
return 0, nil
}
@ -39,11 +39,11 @@ func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusInternalServerError, err
}
files = append(files, filepath.Join(c.fi.Path, name))
files = append(files, filepath.Join(c.FI.Path, name))
}
} else {
files = append(files, c.fi.Path)
files = append(files, c.FI.Path)
}
if query == "true" {
@ -89,7 +89,7 @@ func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusInternalServerError, err
}
name := c.fi.Name
name := c.FI.Name
if name == "." || name == "" {
name = "download"
}

12
file.go
View File

@ -108,10 +108,10 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
}
// getListing gets the information about a specific directory and its files.
func (i *file) getListing(c *requestContext, r *http.Request) error {
func (i *file) getListing(c *RequestContext, r *http.Request) error {
// Gets the directory information using the Virtual File System of
// the user configuration.
f, err := c.us.FileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0)
f, err := c.User.FileSystem.OpenFile(context.TODO(), c.FI.VirtualPath, os.O_RDONLY, 0)
if err != nil {
return err
}
@ -130,7 +130,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
for _, f := range files {
name := f.Name()
allowed := c.us.Allowed("/" + name)
allowed := c.User.Allowed("/" + name)
if !allowed {
continue
@ -433,12 +433,14 @@ func editorLanguage(mode string) string {
mode = "asciidoc"
case "rst":
mode = "rst"
case "html", "htm":
mode = "html"
case "html", "htm", "xml":
mode = "htmlmixed"
case "js":
mode = "javascript"
case "go":
mode = "golang"
case "":
mode = "text"
}
return mode

View File

@ -49,7 +49,7 @@ type FileManager struct {
Commands map[string][]string
// The plugins that have been plugged in.
Plugins []*Plugin
Plugins map[string]Plugin
}
// Command is a command function.
@ -111,9 +111,13 @@ type Regexp struct {
}
// Plugin is a File Manager plugin.
type Plugin struct {
type Plugin interface {
// The JavaScript that will be injected into the main page.
JavaScript string
JavaScript() string
// If the Plugin returns (0, nil), the executation of File Manager will procced as usual.
// Otherwise it will stop.
BeforeAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
AfterAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
}
// DefaultUser is used on New, when no 'base' user is provided.
@ -134,13 +138,13 @@ var DefaultUser = User{
// exists, it will load the users from there. Otherwise, a new user
// will be created using the 'base' variable. The 'base' User should
// not have the Password field hashed.
// TODO: should it ask for a baseURL on New????
func New(database string, base User) (*FileManager, error) {
// Creates a new File Manager instance with the Users
// map and Assets box.
m := &FileManager{
Users: map[string]*User{},
assets: rice.MustFindBox("./assets/dist"),
Plugins: map[string]Plugin{},
}
// Tries to open a database on the location provided. This
@ -240,13 +244,33 @@ func (m *FileManager) SetBaseURL(url string) {
m.BaseURL = strings.TrimSuffix(url, "/")
}
// RegisterPlugin registers a plugin to a File Manager instance and
// loads its options from the database.
func (m *FileManager) RegisterPlugin(name string, plugin Plugin) error {
if _, ok := m.Plugins[name]; ok {
return errors.New("Plugin already registred")
}
err := m.db.Get("plugins", name, &plugin)
if err != nil && err == storm.ErrNotFound {
err = m.db.Set("plugins", name, plugin)
}
if err != nil {
return err
}
m.Plugins[name] = plugin
return nil
}
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// TODO: Handle errors here and make it compatible with http.Handler
code, err := serveHTTP(&requestContext{
fm: m,
us: nil,
fi: nil,
code, err := serveHTTP(&RequestContext{
FM: m,
User: nil,
FI: nil,
}, w, r)
if code != 0 && err != nil {

103
http.go
View File

@ -8,20 +8,22 @@ import (
"strings"
)
// requestContext contains the needed information to make handlers work.
type requestContext struct {
us *User
fm *FileManager
fi *file
// RequestContext contains the needed information to make handlers work.
type RequestContext struct {
User *User
FM *FileManager
FI *file
// On API handlers, Router is the APi handler we want. TODO: review this
Router string
}
// serveHTTP is the main entry point of this HTML application.
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
// returns a 404 error because we're not supposed to be here!
p := strings.TrimPrefix(r.URL.Path, c.fm.BaseURL)
p := strings.TrimPrefix(r.URL.Path, c.FM.BaseURL)
if len(p) >= len(r.URL.Path) && c.fm.BaseURL != "" {
if len(p) >= len(r.URL.Path) && c.FM.BaseURL != "" {
return http.StatusNotFound, nil
}
@ -32,9 +34,9 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
if r.URL.Path == "/sw.js" {
return renderFile(
w,
c.fm.assets.MustString(r.URL.Path),
c.FM.assets.MustString(r.URL.Path),
"application/javascript",
c.fm.RootURL(),
c,
)
}
@ -63,29 +65,29 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
return renderFile(
w,
c.fm.assets.MustString("index.html"),
c.FM.assets.MustString("index.html"),
"text/html",
c.fm.RootURL(),
c,
)
}
// staticHandler handles the static assets path.
func staticHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if r.URL.Path != "/static/manifest.json" {
http.FileServer(c.fm.assets.HTTPBox()).ServeHTTP(w, r)
http.FileServer(c.FM.assets.HTTPBox()).ServeHTTP(w, r)
return 0, nil
}
return renderFile(
w,
c.fm.assets.MustString(r.URL.Path),
c.FM.assets.MustString(r.URL.Path),
"application/json",
c.fm.RootURL(),
c,
)
}
// apiHandler is the main entry point for the /api endpoint.
func apiHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if r.URL.Path == "/auth/get" {
return authHandler(c, w, r)
}
@ -99,46 +101,66 @@ func apiHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
return http.StatusForbidden, nil
}
var router string
router, r.URL.Path = cleanURL(r.URL.Path)
c.Router, r.URL.Path = cleanURL(r.URL.Path)
if !c.us.Allowed(r.URL.Path) {
if !c.User.Allowed(r.URL.Path) {
return http.StatusForbidden, nil
}
if router == "checksum" || router == "download" {
for _, p := range c.FM.Plugins {
code, err := p.BeforeAPI(c, w, r)
if code != 0 || err != nil {
return code, err
}
}
if c.Router == "checksum" || c.Router == "download" {
var err error
c.fi, err = getInfo(r.URL, c.fm, c.us)
c.FI, err = getInfo(r.URL, c.FM, c.User)
if err != nil {
return errorToHTTP(err, false), err
}
}
switch router {
var code int
var err error
switch c.Router {
case "download":
return downloadHandler(c, w, r)
code, err = downloadHandler(c, w, r)
case "checksum":
return checksumHandler(c, w, r)
code, err = checksumHandler(c, w, r)
case "command":
return command(c, w, r)
code, err = command(c, w, r)
case "search":
return search(c, w, r)
code, err = search(c, w, r)
case "resource":
return resourceHandler(c, w, r)
code, err = resourceHandler(c, w, r)
case "users":
return usersHandler(c, w, r)
code, err = usersHandler(c, w, r)
case "commands":
return commandsHandler(c, w, r)
code, err = commandsHandler(c, w, r)
}
return http.StatusNotFound, nil
if code >= 300 || err != nil {
return code, err
}
for _, p := range c.FM.Plugins {
code, err := p.AfterAPI(c, w, r)
if code != 0 || err != nil {
return code, err
}
}
return code, err
}
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
func checksumHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
query := r.URL.Query().Get("algo")
val, err := c.fi.Checksum(query)
val, err := c.FI.Checksum(query)
if err == errInvalidOption {
return http.StatusBadRequest, err
} else if err != nil {
@ -167,11 +189,20 @@ func cleanURL(path string) (string, string) {
}
// renderFile renders a file using a template with some needed variables.
func renderFile(w http.ResponseWriter, file string, contentType string, baseURL string) (int, error) {
tpl := template.Must(template.New("file").Parse(file))
func renderFile(w http.ResponseWriter, file string, contentType string, c *RequestContext) (int, error) {
functions := template.FuncMap{
"JS": func(s string) template.JS {
return template.JS(s)
},
}
tpl := template.Must(template.New("file").Funcs(functions).Parse(file))
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
err := tpl.Execute(w, map[string]string{"BaseURL": baseURL})
err := tpl.Execute(w, map[string]interface{}{
"BaseURL": c.FM.RootURL(),
"Plugins": c.FM.Plugins,
})
if err != nil {
return http.StatusInternalServerError, err
}

View File

@ -12,7 +12,7 @@ import (
"strings"
)
func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
switch r.Method {
case http.MethodGet:
return resourceGetHandler(c, w, r)
@ -20,8 +20,8 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return resourceDeleteHandler(c, w, r)
case http.MethodPut:
// Before save command handler.
path := filepath.Join(string(c.us.FileSystem), r.URL.Path)
if err := c.fm.Runner("before_save", path); err != nil {
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
if err := c.FM.Runner("before_save", path); err != nil {
return http.StatusInternalServerError, err
}
@ -31,7 +31,7 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
}
// After save command handler.
if err := c.fm.Runner("after_save", path); err != nil {
if err := c.FM.Runner("after_save", path); err != nil {
return http.StatusInternalServerError, err
}
@ -45,9 +45,9 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusNotImplemented, nil
}
func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Obtains the information of the directory/file.
f, err := getInfo(r.URL, c.fm, c.us)
f, err := getInfo(r.URL, c.FM, c.User)
if err != nil {
return errorToHTTP(err, false), err
}
@ -60,7 +60,7 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
// If it is a dir, go and serve the listing.
if f.IsDir {
c.fi = f
c.FI = f
return listingHandler(c, w, r)
}
@ -71,7 +71,7 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
// If it can't be edited or the user isn't allowed to,
// serve it as a listing, with a preview of the file.
if !f.CanBeEdited() || !c.us.AllowEdit {
if !f.CanBeEdited() || !c.User.AllowEdit {
f.Kind = "preview"
} else {
// Otherwise, we just bring the editor in!
@ -86,8 +86,8 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
return renderJSON(w, f)
}
func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
f := c.fi
func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
f := c.FI
f.Kind = "listing"
err := f.getListing(c, r)
@ -97,7 +97,7 @@ func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (
listing := f.listing
cookieScope := c.fm.RootURL()
cookieScope := c.FM.RootURL()
if cookieScope == "" {
cookieScope = "/"
}
@ -114,14 +114,14 @@ func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (
return renderJSON(w, f)
}
func resourceDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func resourceDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Prevent the removal of the root directory.
if r.URL.Path == "/" {
return http.StatusForbidden, nil
}
// Remove the file or folder.
err := c.us.FileSystem.RemoveAll(context.TODO(), r.URL.Path)
err := c.User.FileSystem.RemoveAll(context.TODO(), r.URL.Path)
if err != nil {
return errorToHTTP(err, true), err
}
@ -129,7 +129,7 @@ func resourceDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Req
return http.StatusOK, nil
}
func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Checks if the current request is for a directory and not a file.
if strings.HasSuffix(r.URL.Path, "/") {
// If the method is PUT, we return 405 Method not Allowed, because
@ -139,7 +139,7 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
}
// Otherwise we try to create the directory.
err := c.us.FileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
err := c.User.FileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
return errorToHTTP(err, false), err
}
@ -147,13 +147,13 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
// desirable to override an already existent file. Thus, we check
// if the file already exists. If so, we just return a 409 Conflict.
if r.Method == http.MethodPost {
if _, err := c.us.FileSystem.Stat(context.TODO(), r.URL.Path); err == nil {
if _, err := c.User.FileSystem.Stat(context.TODO(), r.URL.Path); err == nil {
return http.StatusConflict, errors.New("There is already a file on that path")
}
}
// Create/Open the file.
f, err := c.us.FileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
f, err := c.User.FileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
defer f.Close()
if err != nil {
@ -178,7 +178,7 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
return http.StatusOK, nil
}
func resourcePatchHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func resourcePatchHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
dst := r.Header.Get("Destination")
dst, err := url.QueryUnescape(dst)
if err != nil {
@ -191,7 +191,7 @@ func resourcePatchHandler(c *requestContext, w http.ResponseWriter, r *http.Requ
return http.StatusForbidden, nil
}
err = c.us.FileSystem.Rename(context.TODO(), src, dst)
err = c.User.FileSystem.Rename(context.TODO(), src, dst)
return errorToHTTP(err, true), err
}

File diff suppressed because one or more lines are too long

View File

@ -43,7 +43,7 @@ func parseSearch(value string) *searchOptions {
}
// search searches for a file or directory.
func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func search(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
// Upgrades the connection to a websocket and checks for errors.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
@ -73,7 +73,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
search = parseSearch(value)
scope := strings.TrimPrefix(r.URL.Path, "/")
scope = "/" + scope
scope = string(c.us.FileSystem) + scope
scope = string(c.User.FileSystem) + scope
scope = strings.Replace(scope, "\\", "/", -1)
scope = filepath.Clean(scope)
@ -93,7 +93,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
}
if strings.Contains(path, term) {
if !c.us.Allowed(path) {
if !c.User.Allowed(path) {
return nil
}

View File

@ -6,7 +6,7 @@ import (
"net/http"
)
func commandsHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func commandsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
switch r.Method {
case http.MethodGet:
return commandsGetHandler(c, w, r)
@ -17,16 +17,16 @@ func commandsHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusMethodNotAllowed, nil
}
func commandsGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin {
func commandsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin {
return http.StatusForbidden, nil
}
return renderJSON(w, c.fm.Commands)
return renderJSON(w, c.FM.Commands)
}
func commandsPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin {
func commandsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin {
return http.StatusForbidden, nil
}
@ -42,10 +42,10 @@ func commandsPutHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
return http.StatusBadRequest, errors.New("Invalid JSON")
}
if err := c.fm.db.Set("config", "commands", commands); err != nil {
if err := c.FM.db.Set("config", "commands", commands); err != nil {
return http.StatusInternalServerError, err
}
c.fm.Commands = commands
c.FM.Commands = commands
return http.StatusOK, nil
}

View File

@ -11,7 +11,7 @@ import (
"github.com/asdine/storm"
)
func usersHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
switch r.Method {
case http.MethodGet:
return usersGetHandler(c, w, r)
@ -29,8 +29,8 @@ func usersHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (in
// usersGetHandler is used to handle the GET requests for /api/users. It can print a list
// of users or a specific user. The password hash is always removed before being sent to the
// client.
func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin {
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin {
return http.StatusForbidden, nil
}
@ -38,7 +38,7 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
if r.URL.Path == "/" {
users := []User{}
for _, user := range c.fm.Users {
for _, user := range c.FM.Users {
// Copies the user and removes the password.
u := *user
u.Password = ""
@ -62,7 +62,7 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
}
// Searches for the user and prints the one who matches.
for _, user := range c.fm.Users {
for _, user := range c.FM.Users {
if user.ID != id {
continue
}
@ -76,8 +76,8 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusNotFound, nil
}
func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin {
func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin {
return http.StatusForbidden, nil
}
@ -128,7 +128,7 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
u.Password = pw
// Saves the user to the database.
err = c.fm.db.Save(&u)
err = c.FM.db.Save(&u)
if err == storm.ErrAlreadyExists {
return http.StatusConflict, err
}
@ -138,7 +138,7 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
}
// Saves the user to the memory.
c.fm.Users[u.Username] = &u
c.FM.Users[u.Username] = &u
// Set the Location header and return.
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
@ -146,8 +146,8 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return 0, nil
}
func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin {
func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin {
return http.StatusForbidden, nil
}
@ -165,7 +165,7 @@ func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
return http.StatusNotFound, err
}
err = c.fm.db.DeleteStruct(&User{ID: id})
err = c.FM.db.DeleteStruct(&User{ID: id})
if err == storm.ErrNotFound {
return http.StatusNotFound, err
}
@ -174,17 +174,17 @@ func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
return http.StatusInternalServerError, err
}
for _, user := range c.fm.Users {
for _, user := range c.FM.Users {
if user.ID == id {
delete(c.fm.Users, user.Username)
delete(c.FM.Users, user.Username)
}
}
return http.StatusOK, nil
}
func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.us.Admin && !(r.URL.Path == "/change-password" || r.URL.Path == "/change-css") {
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if !c.User.Admin && !(r.URL.Path == "/change-password" || r.URL.Path == "/change-css") {
return http.StatusForbidden, nil
}
@ -225,8 +225,8 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
return http.StatusInternalServerError, err
}
c.us.Password = pw
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "Password", pw)
c.User.Password = pw
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
if err != nil {
return http.StatusInternalServerError, err
}
@ -235,8 +235,8 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
}
if sid == "change-css" {
c.us.CSS = u.CSS
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "CSS", u.CSS)
c.User.CSS = u.CSS
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
if err != nil {
return http.StatusInternalServerError, err
}
@ -259,7 +259,7 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
u.Commands = []string{}
}
ouser, ok := c.fm.Users[u.Username]
ouser, ok := c.FM.Users[u.Username]
if !ok {
return http.StatusNotFound, nil
}
@ -279,11 +279,11 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
// Updates the whole User struct because we always are supposed
// to send a new entire object.
err = c.fm.db.Save(&u)
err = c.FM.db.Save(&u)
if err != nil {
return http.StatusInternalServerError, err
}
c.fm.Users[u.Username] = &u
c.FM.Users[u.Username] = &u
return http.StatusOK, nil
}