add user config page and admin menu

This commit is contained in:
2019-12-17 19:28:30 +01:00
parent 4c33b0841e
commit 7653470a7c
16 changed files with 648 additions and 107 deletions

20
go.mod Normal file
View File

@@ -0,0 +1,20 @@
module fornaxian.com/pixeldrain-web
go 1.13
require (
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3
github.com/google/uuid v1.1.1
github.com/julienschmidt/httprouter v1.3.0
github.com/k0kubun/pp v3.0.1+incompatible // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect
github.com/microcosm-cc/bluemonday v1.0.2
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
gopkg.in/russross/blackfriday.v2 v2.0.0
)

115
go.sum Normal file
View File

@@ -0,0 +1,115 @@
fornaxian.com/pixeldrain-api v0.0.0-20191213180911-5823f7064801/go.mod h1:bhJgwXdFTFrZUdQ+vBXzUQiAhvuFnEgZgxeXBhWddLU=
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681 h1:tLIj6RhZHsJW1riDUZj7c4UQ3Gojl8Sh6CHj9cC4nzQ=
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681/go.mod h1:HooOANQfnTseH1XR/kJ2R8sKpPM68GdzHQyHiUlaZqI=
fornaxian.com/pixeldrain-web v0.0.0-20191211101305-cc3c5d58e432/go.mod h1:UwobVC5YVBOtfTc6eBSBjD2tdLQ+xYYp7+web6nCjD0=
fornaxian.com/pixelstore v0.0.0-20191212224440-0453456df082/go.mod h1:saC4IwAOpIip/M7fURYMTcUo5fhwp/m5GmvoQucSwLQ=
fornaxian.com/pixelstore v0.0.0-20191213205107-7cc26c05cb69/go.mod h1:6cdebY9AhneUTQZQ+dyXnIZmMYxCAVgJ5lCeUTWPR8M=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70 h1:yRkXab8h+BAWEphLE0qexJVxIOdPgw+3T9VSLuyPXus=
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70/go.mod h1:Ig5am30IOP/eqsjogI1TuSlOTIeTPHoMOpYYM1bisww=
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3 h1:PfKr7anK3z4kLG9V6BbbKOVFhVaGEAJi4HxXCAa+QeU=
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3/go.mod h1:jdnyerqAlXJJpQmpyrdmSYMitRaRZ8RejEXuXz6n5QY=
github.com/Fornaxian/unifilter v0.0.0-20180623154047-e65e144d5942/go.mod h1:ofV5syadd2nI4gOc/rP1yPnXkARgm+E1D/U38mbUj44=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/threefish v0.0.0-20120919164726-3ecf4c494abf/go.mod h1:bXVurdTuvOiJu7NHALemFe0JMvC2UmwYHW+7fcZaZ2M=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/lib/pq v1.0.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.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
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/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
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.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8 h1:gw/M1/pCu7oELGHZ6rvktNmMbdWhf9kHc7WYrbLeKdo=
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8/go.mod h1:oqLl90kSlp4+8wMQKql9ZdQGa4/5pVCxOOpTVWkoyV0=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xtaci/smux v1.3.3/go.mod h1:f+nYm6SpuHMy/SH0zpbvAFHT1QoMcgLOsWcFip5KfPw=
gitlab.com/NebulousLabs/Sia v1.4.1/go.mod h1:pmBBguXJl2nxajST2OtRv0FOIMSggtn5evGpE9Pju3Y=
gitlab.com/NebulousLabs/demotemutex v0.0.0-20151003192217-235395f71c40/go.mod h1:HfnnxM8isYA7FUlqS5h34XTeiBhPtcuCquVujKsn9aw=
gitlab.com/NebulousLabs/entropy-mnemonics v0.0.0-20181018051301-7532f67e3500/go.mod h1:4koft3fRXTETovKPTeX/Aggj+ajCGWCcuuBBc598Pcs=
gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8=
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA=
gitlab.com/NebulousLabs/go-upnp v0.0.0-20181011194642-3a71999ed0d3/go.mod h1:sleOmkovWsDEQVYXmOJhx69qheoMTmCuPYyiCFCihlg=
gitlab.com/NebulousLabs/merkletree v0.0.0-20190207030457-bc4a11e31a0d/go.mod h1:xItahGeKIkh9BQfxDEX6O3eWxOxbLBPX738sXm0uVaQ=
gitlab.com/NebulousLabs/ratelimit v0.0.0-20180716154200-1308156c2eaf/go.mod h1:vowDA1cdvtWW678ugB7L/yKT2pCN37aH6zYp9NF5Isc=
gitlab.com/NebulousLabs/threadgroup v0.0.0-20180716154133-88a11db9e46c/go.mod h1:w05nvlkvHlk3Vfc7mcU29Toic1X0BcYUnKoTHS0ea2Y=
gitlab.com/NebulousLabs/writeaheadlog v0.0.0-20190703190009-cb822c37bc94/go.mod h1:Lhpa9AcbWcYKcc4amZsOHqJdQglnkWrGuUI68XC7U2Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/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=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,5 +1,7 @@
package pixelapi package pixelapi
import "net/url"
// IsAdmin is the response to the /admin/is_admin API // IsAdmin is the response to the /admin/is_admin API
type IsAdmin struct { type IsAdmin struct {
Success bool `json:"success"` Success bool `json:"success"`
@@ -14,3 +16,31 @@ func (p *PixelAPI) UserIsAdmin() (resp IsAdmin, err error) {
} }
return resp, nil return resp, nil
} }
// AdminGlobal is a global setting in pixeldrain's back-end
type AdminGlobal struct {
Key string `json:"key"`
Value string `json:"value"`
}
// AdminGlobals is an array of globals
type AdminGlobals struct {
Success bool `json:"success"`
Globals []AdminGlobal `json:"globals"`
}
// AdminGetGlobals returns if the logged in user is an admin user
func (p *PixelAPI) AdminGetGlobals() (resp AdminGlobals, err error) {
if err = p.jsonRequest("GET", p.apiEndpoint+"/admin/globals", &resp); err != nil {
return resp, err
}
return resp, nil
}
// AdminSetGlobals returns if the logged in user is an admin user
func (p *PixelAPI) AdminSetGlobals(key, value string) (resp SuccessResponse, err error) {
var form = url.Values{}
form.Add("key", key)
form.Add("value", value)
return resp, p.form("POST", p.apiEndpoint+"/admin/globals", form, &resp, true)
}

View File

@@ -164,7 +164,6 @@ func (p *PixelAPI) form(
} }
func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error { func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error {
var jdec = json.NewDecoder(resp.Body)
var err error var err error
// Test for client side and server side errors // Test for client side and server side errors
@@ -172,8 +171,7 @@ func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool
var errResp = Error{ var errResp = Error{
ReqError: false, ReqError: false,
} }
err = jdec.Decode(&errResp) if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
if err != nil {
log.Error("Can't decode this: %v", err) log.Error("Can't decode this: %v", err)
return Error{ return Error{
ReqError: true, ReqError: true,
@@ -185,8 +183,11 @@ func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool
return errResp return errResp
} }
err = jdec.Decode(target) if target == nil {
if err != nil { return nil
}
if err = json.NewDecoder(resp.Body).Decode(target); err != nil {
r, _ := ioutil.ReadAll(resp.Body) r, _ := ioutil.ReadAll(resp.Body)
log.Error("Can't decode this: %v. %s", err, r) log.Error("Can't decode this: %v. %s", err, r)
return Error{ return Error{

View File

@@ -3,6 +3,7 @@ package pixelapi
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"strconv"
) )
// Registration is the response to the UserRegister API. The register API can // Registration is the response to the UserRegister API. The register API can
@@ -66,6 +67,7 @@ func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp *Log
type UserInfo struct { type UserInfo struct {
Success bool `json:"success"` Success bool `json:"success"`
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"`
} }
// UserInfo returns information about the logged in user. Requires an API key // UserInfo returns information about the logged in user. Requires an API key
@@ -80,13 +82,8 @@ func (p *PixelAPI) UserInfo() (resp *UserInfo, err error) {
// UserSessionDestroy destroys an API key so it can no longer be used to perform // UserSessionDestroy destroys an API key so it can no longer be used to perform
// actions // actions
func (p *PixelAPI) UserSessionDestroy(key string) (resp *SuccessResponse, err error) { func (p *PixelAPI) UserSessionDestroy(key string) (err error) {
resp = &SuccessResponse{} return p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", nil)
err = p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", resp)
if err != nil {
return nil, err
}
return resp, nil
} }
type UserFiles struct { type UserFiles struct {
@@ -114,25 +111,64 @@ type UserLists struct {
func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) { func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) {
resp = &UserLists{Lists: make([]List, 0)} resp = &UserLists{Lists: make([]List, 0)}
err = p.jsonRequest( if err = p.jsonRequest(
"GET", "GET",
fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit), fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit),
resp, resp,
) ); err != nil {
if err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
} }
func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (resp *SuccessResponse, err error) { func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (err error) {
resp = &SuccessResponse{}
var form = url.Values{} var form = url.Values{}
form.Add("old_password", oldPW) form.Add("old_password", oldPW)
form.Add("new_password", newPW) form.Add("new_password", newPW)
err = p.form("PUT", p.apiEndpoint+"/user/password", form, resp, true) return p.form("PUT", p.apiEndpoint+"/user/password", form, nil, true)
if err != nil {
return nil, err
} }
return resp, nil
// UserEmailReset starts the e-mail change process. An email will be sent to the
// new address to verify that it's real. Once the link in the e-mail is clicked
// the key it contains can be sent to the API with UserEmailResetConfirm and the
// change will be applied
func (p *PixelAPI) UserEmailReset(email string, delete bool) (err error) {
var form = url.Values{}
form.Add("new_email", email)
form.Add("delete", strconv.FormatBool(delete))
return p.form("PUT", p.apiEndpoint+"/user/email_reset", form, nil, true)
}
// UserEmailResetConfirm finishes process of changing a user's e-mail address
func (p *PixelAPI) UserEmailResetConfirm(key string) (err error) {
var form = url.Values{}
form.Add("key", key)
return p.form("PUT", p.apiEndpoint+"/user/email_reset_cofirm", form, nil, true)
}
// UserPasswordReset starts the password reset process. An email will be sent
// the user to verify that it really wanted to reset the password. Once the link
// in the e-mail is clicked the key it contains can be sent to the API with
// UserPasswordResetConfirm and a new password can be set
func (p *PixelAPI) UserPasswordReset(email string, recaptchaResponse string) (err error) {
var form = url.Values{}
form.Add("email", email)
form.Add("recaptcha_response", recaptchaResponse)
return p.form("PUT", p.apiEndpoint+"/user/password_reset", form, nil, true)
}
// UserPasswordResetConfirm finishes process of resetting a user's password. If
// the key is valid the new_password parameter will be saved as the new password
func (p *PixelAPI) UserPasswordResetConfirm(key string, newPassword string) (err error) {
var form = url.Values{}
form.Add("key", key)
form.Add("new_password", newPassword)
return p.form("PUT", p.apiEndpoint+"/user/password_reset_confirm", form, nil, true)
}
// UserSetUsername changes the user's username.
func (p *PixelAPI) UserSetUsername(username string) (err error) {
var form = url.Values{}
form.Add("new_username", username)
return p.form("PUT", p.apiEndpoint+"/user/username", form, nil, true)
} }

View File

@@ -0,0 +1,37 @@
{{define "email_confirm"}}<!DOCTYPE html>
<html>
<head>
{{template "meta_tags" "E-mail verification"}}
{{template "user_style" .}}
</head>
<body>
{{template "page_top" .}}
<div class="page_content">
<div class="limit_width">
{{if eq .Other "success"}}
<h1>Success!</h1>
<p>
Your account's e-mail address has been updated.
</p>
{{else if eq .Other "not_found"}}
<h1>E-mail change failed</h1>
<p>
This e-mail change request does not exist or has expired.
Please try again if you still want to change your e-mail
address.
</p>
{{else}}
<h1>Error</h1>
<p>
Something went wrong while processing this request. Please
try again later.
</p>
{{end}}
</div>
</div>
{{template "page_bottom" .}}
{{template "analytics"}}
</body>
</html>
{{end}}

View File

@@ -12,11 +12,12 @@
<h1>Welcome home, {{.Username}}!</h1> <h1>Welcome home, {{.Username}}!</h1>
<div class="page_content"><div class="limit_width"> <div class="page_content"><div class="limit_width">
<h2>Actions</h2> <h2>Account information</h2>
<ul> <ul>
<li><a href="/user/change_password" class="button">Change my password</a></li> <li>Username: {{.Username}}</li>
<li><a href="/logout" class="button">Log out</a></li> <li>E-mail address: {{.Email}}</li>
</ul> </ul>
<a href="/user/settings" class="button">Update account settings</a>
<h2>Your most recently uploaded files:</h2> <h2>Your most recently uploaded files:</h2>
<div class="highlight_dark"> <div class="highlight_dark">

View File

@@ -8,14 +8,11 @@
<body> <body>
{{template "page_top" .}} {{template "page_top" .}}
<h1>User configuration</h1> <h1>{{.Title}}</h1>
<div class="page_content"><div class="limit_width"> <div class="page_content"><div class="limit_width">
{{template "form" .Other.PasswordForm}}
<p>What would you like to do?</p> {{template "form" .Other.EmailForm}}
<ul> {{template "form" .Other.UsernameForm}}
<li><a href="/user/change_password">Change my password</a></li>
</ul>
</div></div> </div></div>
{{template "page_bottom" .}} {{template "page_bottom" .}}
{{template "analytics"}} {{template "analytics"}}

View File

@@ -77,7 +77,14 @@
}, },
ticks: { ticks: {
callback: function(value, index, values) { callback: function(value, index, values) {
return Math.round((value*8/1e6)/(interval*60)) + " Mbps"; if (value > 1e12) {
return Math.round(value/1e9)/1e3 + " TB";
} else if (value > 1e9) {
return Math.round(value/1e6)/1e3 + " GB";
} else if (value > 1e6) {
return Math.round(value/1e3)/1e3 + " MB";
}
return value/1e3 + " kB";
} }
}, },
gridLines: { gridLines: {
@@ -127,6 +134,10 @@
setData(); setData();
</script> </script>
<hr/>
<div class="limit_width">
<a href="/admin/globals">Update global settings</a>
</div>
{{else}} {{else}}
<h1 style="text-align: center;">;)</h1> <h1 style="text-align: center;">;)</h1>
{{end}} {{end}}

View File

@@ -1,6 +1,7 @@
{{define "form"}} {{define "form"}}
<h1>{{.Title}}</h1> <h1>{{.Title}}</h1>
{{.PreFormHTML}} {{.PreFormHTML}}
<form class="highlight_dark" method="POST">
{{if eq .Submitted true}} {{if eq .Submitted true}}
{{if eq .SubmitSuccess true}} {{if eq .SubmitSuccess true}}
<div id="submit_result" class="highlight_green"> <div id="submit_result" class="highlight_green">
@@ -17,8 +18,6 @@
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
<form class="highlight_dark" method="POST">
<input type="text" name="form" value="{{.Name}}" style="display: none;" readonly="readonly"/> <input type="text" name="form" value="{{.Name}}" style="display: none;" readonly="readonly"/>
{{if ne .Username ""}} {{if ne .Username ""}}
<!-- The invisible username field is so browsers know which user the form was for --> <!-- The invisible username field is so browsers know which user the form was for -->
@@ -27,10 +26,18 @@
<table class="form"> <table class="form">
{{range $index, $field := .Fields}} {{range $index, $field := .Fields}}
<tr class="form"> <tr class="form">
{{if eq $field.Type "textarea"}}
<td colspan="2">
{{$field.Label}}<br/>
<textarea id="input_{{$field.Name}}" name="{{$field.Name}}" class="form_input" style="width: 100%; height: 5em; resize: vertical;">{{$field.DefaultValue}}</textarea>
</td>
{{else}}
<td>{{$field.Label}}</td> <td>{{$field.Label}}</td>
<td> <td>
{{if eq $field.Type "text"}} {{if eq $field.Type "text"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" class="form_input"/> <input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" class="form_input"/>
{{else if eq $field.Type "number"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="number" class="form_input"/>
{{else if eq $field.Type "username"}} {{else if eq $field.Type "username"}}
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" autocomplete="username" class="form_input"/> <input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" autocomplete="username" class="form_input"/>
{{else if eq $field.Type "email"}} {{else if eq $field.Type "email"}}
@@ -44,6 +51,7 @@
<div class="g-recaptcha" data-theme="dark" data-sitekey="{{$field.CaptchaSiteKey}}"></div> <div class="g-recaptcha" data-theme="dark" data-sitekey="{{$field.CaptchaSiteKey}}"></div>
{{end}} {{end}}
</td> </td>
{{end}}
{{if or (ne $field.Description "") (eq $field.Separator true)}} {{if or (ne $field.Description "") (eq $field.Separator true)}}
<tr class="form"> <tr class="form">
<td colspan="2"> <td colspan="2">

View File

@@ -0,0 +1,97 @@
package webcontroller
import (
"fmt"
"html/template"
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
)
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f forms.Form) {
if isAdmin, err := td.PixelAPI.UserIsAdmin(); err != nil {
td.Title = err.Error()
return forms.Form{Title: td.Title}
} else if !isAdmin.IsAdmin {
td.Title = ";)"
return forms.Form{Title: td.Title}
}
td.Title = "Pixeldrain global configuration"
f = forms.Form{
Name: "admin_globals",
Title: td.Title,
PreFormHTML: template.HTML("<p>Careful! The slightest typing error could bring the whole website down</p>"),
BackLink: "/admin",
SubmitLabel: "Submit",
}
globals, err := td.PixelAPI.AdminGetGlobals()
if err != nil {
f.SubmitMessages = []template.HTML{template.HTML(err.Error())}
return f
}
var globalsMap = make(map[string]string)
for _, v := range globals.Globals {
f.Fields = append(f.Fields, forms.Field{
Name: v.Key,
DefaultValue: v.Value,
Label: v.Key,
Type: func() forms.FieldType {
switch v.Key {
case
"email_address_change_body",
"email_password_reset_body":
return forms.FieldTypeTextarea
case
"api_ratelimit_limit",
"api_ratelimit_rate",
"cron_interval_seconds",
"file_inactive_expiry_days",
"max_file_size",
"pixelstore_min_redundancy":
return forms.FieldTypeNumber
default:
return forms.FieldTypeText
}
}(),
})
globalsMap[v.Key] = v.Value
}
if f.ReadInput(r) {
var successfulUpdates = 0
for k, v := range f.Fields {
if v.EnteredValue == globalsMap[v.Name] {
continue // Change changes, no need to update
}
// Value changed, try to update global setting
if _, err = td.PixelAPI.AdminSetGlobals(v.Name, v.EnteredValue); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = append(f.SubmitMessages, template.HTML(apiErr.Message))
} else {
log.Error("%s", err)
f.SubmitMessages = append(f.SubmitMessages, template.HTML(
fmt.Sprintf("Failed to set '%s': %s", v.Name, err),
))
return f
}
} else {
f.Fields[k].DefaultValue = v.EnteredValue
successfulUpdates++
}
}
if len(f.SubmitMessages) == 0 {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{template.HTML(
fmt.Sprintf("Success! %d values updated", successfulUpdates),
)}
}
}
return f
}

View File

@@ -79,6 +79,8 @@ type FieldType string
// Fields which can be in a form // Fields which can be in a form
const ( const (
FieldTypeText FieldType = "text" FieldTypeText FieldType = "text"
FieldTypeTextarea FieldType = "textarea"
FieldTypeNumber FieldType = "number"
FieldTypeUsername FieldType = "username" FieldTypeUsername FieldType = "username"
FieldTypeEmail FieldType = "email" FieldTypeEmail FieldType = "email"
FieldTypeCurrentPassword FieldType = "current-password" FieldTypeCurrentPassword FieldType = "current-password"

View File

@@ -16,6 +16,7 @@ import (
type TemplateData struct { type TemplateData struct {
Authenticated bool Authenticated bool
Username string Username string
Email string
UserAgent string UserAgent string
UserStyle template.CSS UserStyle template.CSS
APIEndpoint template.URL APIEndpoint template.URL
@@ -67,6 +68,7 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
// Authentication succeeded // Authentication succeeded
t.Authenticated = true t.Authenticated = true
t.Username = uinf.Username t.Username = uinf.Username
t.Email = uinf.Email
} else { } else {
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "") t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
} }

View File

@@ -18,9 +18,8 @@ func (wc *WebController) serveLogout(
) { ) {
if key, err := wc.getAPIKey(r); err == nil { if key, err := wc.getAPIKey(r); err == nil {
var api = pixelapi.New(wc.conf.APIURLInternal, key) var api = pixelapi.New(wc.conf.APIURLInternal, key)
_, err1 := api.UserSessionDestroy(key) if err = api.UserSessionDestroy(key); err != nil {
if err1 != nil { log.Warn("logout failed for session '%s': %s", key, err)
log.Warn("logout failed for session '%s': %s", key, err1)
} }
} }
@@ -151,9 +150,12 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
BackLink: "/", BackLink: "/",
SubmitLabel: "Login", SubmitLabel: "Login",
PostFormHTML: template.HTML( PostFormHTML: template.HTML(
`<br/>If you don't have a pixeldrain account yet, you can ` + `<p>If you don't have a pixeldrain account yet, you can ` +
`<a href="/register">register here</a>. No e-mail address is ` + `<a href="/register">register here</a>. No e-mail address is ` +
`required.<br/>`, `required.</p>` +
`<p>Forgot your password? If your account has a valid e-mail ` +
`address you can <a href="/password_reset">request a new ` +
`password here</a>.</p>`,
), ),
} }
@@ -184,45 +186,35 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
return f return f
} }
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) { func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f forms.Form) {
td.Title = "Change Password" td.Title = "Recover lost password"
f = forms.Form{ f = forms.Form{
Name: "password_change", Name: "password_reset",
Title: td.Title, Title: td.Title,
Fields: []forms.Field{ Fields: []forms.Field{
{ {
Name: "old_password", Name: "email",
Label: "Old Password", Label: "E-mail address",
Type: forms.FieldTypeCurrentPassword, Description: "we will send a password reset link to this " +
"e-mail address",
Separator: true,
Type: forms.FieldTypeEmail,
}, { }, {
Name: "new_password1", Name: "recaptcha_response",
Label: "New Password", Label: "Turing test (click the white box)",
Type: forms.FieldTypeNewPassword, Description: "the reCaptcha turing test verifies that you " +
}, { "are not an evil robot that is trying hijack accounts",
Name: "new_password2", Separator: true,
Label: "New Password verification", Type: forms.FieldTypeCaptcha,
Type: forms.FieldTypeCurrentPassword, CaptchaSiteKey: wc.captchaKey(),
}, },
}, },
BackLink: "/user", BackLink: "/login",
SubmitLabel: "Submit", SubmitLabel: "Submit",
} }
if f.ReadInput(r) { if f.ReadInput(r) {
if f.FieldVal("new_password1") != f.FieldVal("new_password2") { if err := td.PixelAPI.UserPasswordReset(f.FieldVal("email"), f.FieldVal("recaptcha_response")); err != nil {
f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " +
"password in both new password fields"}
return f
}
// Passwords match, send the request and fill in the response in the
// form
_, err := td.PixelAPI.UserPasswordSet(
f.FieldVal("old_password"),
f.FieldVal("new_password1"),
)
if err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)} f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else { } else {
@@ -230,9 +222,8 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f form
f.SubmitMessages = []template.HTML{"Internal Server Error"} f.SubmitMessages = []template.HTML{"Internal Server Error"}
} }
} else { } else {
// Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"} f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
} }
} }
return f return f

View File

@@ -0,0 +1,188 @@
package webcontroller
import (
"html/template"
"net/http"
"fornaxian.com/pixeldrain-web/pixelapi"
"fornaxian.com/pixeldrain-web/webcontroller/forms"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
func (wc *WebController) serveUserSettings(
w http.ResponseWriter,
r *http.Request,
p httprouter.Params,
) {
td := wc.newTemplateData(w, r)
if !td.Authenticated {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
td.Title = "Account settings"
td.Other = struct {
PasswordForm forms.Form
EmailForm forms.Form
UsernameForm forms.Form
}{
PasswordForm: wc.passwordForm(td, r),
EmailForm: wc.emailForm(td, r),
UsernameForm: wc.usernameForm(td, r),
}
wc.templates.Get().ExecuteTemplate(w, "user_settings", td)
}
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "password_change",
Title: "Change password",
Fields: []forms.Field{
{
Name: "old_password",
Label: "Old Password",
Type: forms.FieldTypeCurrentPassword,
}, {
Name: "new_password1",
Label: "New Password",
Type: forms.FieldTypeNewPassword,
}, {
Name: "new_password2",
Label: "New Password again",
Description: "we need you to repeat your password so you " +
"won't be locked out of your account if you make a " +
"typing error",
Type: forms.FieldTypeNewPassword,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if f.FieldVal("new_password1") != f.FieldVal("new_password2") {
f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " +
"password in both new password fields"}
return f
}
// Passwords match, send the request and fill in the response in the
// form
if err := td.PixelAPI.UserPasswordSet(
f.FieldVal("old_password"),
f.FieldVal("new_password1"),
); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"}
}
}
return f
}
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "email_change",
Title: "Change e-mail address",
Fields: []forms.Field{
{
Name: "new_email",
Label: "New e-mail address",
Description: "we will send an e-mail to the new address to " +
"verify that it's real. The address will be saved once " +
"the link in the message is clicked. If the e-mail " +
"doesn't arrive right away please check your spam box too",
Type: forms.FieldTypeEmail,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if err := td.PixelAPI.UserEmailReset(
f.FieldVal("new_email"),
false,
); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
}
}
return f
}
func (wc *WebController) serveEmailConfirm(
w http.ResponseWriter,
r *http.Request,
p httprouter.Params,
) {
var status string
if key, err := wc.getAPIKey(r); err == nil {
err = pixelapi.New(wc.conf.APIURLInternal, key).UserEmailResetConfirm(r.FormValue("key"))
if err != nil && err.Error() == "not_found" {
status = "not_found"
} else if err != nil {
status = "internal_error"
} else {
status = "success"
}
}
td := wc.newTemplateData(w, r)
td.Other = status
wc.templates.Get().ExecuteTemplate(w, "email_confirm", td)
}
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f forms.Form) {
f = forms.Form{
Name: "username_change",
Title: "Change username",
Fields: []forms.Field{
{
Name: "new_username",
Label: "New username",
Description: "changing your username also changes the name " +
"used to log in. If you forget your username you can " +
"still log in using your e-mail address if you have one " +
"configured",
Type: forms.FieldTypeUsername,
},
},
SubmitLabel: "Submit",
}
if f.ReadInput(r) {
if err := td.PixelAPI.UserSetUsername(f.FieldVal("new_username")); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else {
// Request was a success
f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{template.HTML(
"Success! You are now " + f.FieldVal("new_username"),
)}
}
}
return f
}

View File

@@ -79,6 +79,8 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
r.POST(p+"/register" /* */, wc.serveForm(wc.registerForm, false)) r.POST(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
r.GET(p+"/login" /* */, wc.serveForm(wc.loginForm, false)) r.GET(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false)) r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
r.GET(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
r.POST(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true)) r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true))
r.POST(p+"/logout" /* */, wc.serveLogout) r.POST(p+"/logout" /* */, wc.serveLogout)
r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true)) r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true))
@@ -87,11 +89,14 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true)) r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true))
// User account settings // User account settings
r.GET(p+"/user/settings" /* */, wc.serveTemplate("user_settings", true)) r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
r.GET(p+"/user/change_password" /* */, wc.serveForm(wc.passwordForm, true)) r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
r.POST(p+"/user/change_password" /**/, wc.serveForm(wc.passwordForm, true)) r.GET(p+"/user/confirm_email" /**/, wc.serveEmailConfirm)
r.GET(p+"/admin", wc.serveTemplate("admin_panel", true)) // Admin settings
r.GET(p+"/admin" /* */, wc.serveTemplate("admin_panel", true))
r.GET(p+"/admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true))
r.POST(p+"/admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true))
r.NotFound = http.HandlerFunc(wc.serveNotFound) r.NotFound = http.HandlerFunc(wc.serveNotFound)