bunch of API changes

This commit is contained in:
2020-05-05 22:03:34 +02:00
parent e89db822cc
commit e811d0a54f
22 changed files with 12289 additions and 352 deletions

View File

@@ -1,16 +0,0 @@
FROM ubuntu
RUN mkdir -p \
/bin \
/opt/pixeldrain/res
COPY docker/pixeldrain-web /bin/pixeldrain-web
COPY res /opt/pixeldrain/res
EXPOSE 8081
ENV api_url_external="" \
api_url_internal="" \
debug_mode=""
ENTRYPOINT [ "/bin/pixeldrain-web" ]

View File

@@ -1,55 +0,0 @@
package main
import (
"net/http"
"os"
"fornaxian.com/pixeldrain-web/init/conf"
"fornaxian.com/pixeldrain-web/webcontroller"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
// This is just a launcher for the web server. During testing the app would
// be directly embedded by another Go project. And when deployed it will run
// independently.
func main() {
log.Info("Starting web UI server in docker mode (PID %v)", os.Getpid())
var webconf = &conf.PixelWebConfig{
StaticResourceDir: "/opt/pixeldrain/res/static",
TemplateDir: "/opt/pixeldrain/res/template",
}
if webconf.APIURLExternal, _ = os.LookupEnv("api_url_external"); webconf.APIURLExternal == "" {
webconf.APIURLExternal = "/api"
}
if webconf.APIURLInternal, _ = os.LookupEnv("api_url_internal"); webconf.APIURLInternal == "" {
panic("internal API URL is required. Please set api_url_internal")
}
// Check debug mode. When debug mode is enabled debug logging will be
// enabled, templates will be parsed on every request and analytics tracking
// will be disabled
switch d, _ := os.LookupEnv("debug_mode"); d {
case "1", "yes", "y", "true":
webconf.DebugMode = true
log.SetLogLevel(log.LevelDebug)
default:
webconf.DebugMode = false
log.SetLogLevel(log.LevelInfo)
}
// Web path prefix
prefix, _ := os.LookupEnv("api_path_prefix")
// Init the http router
r := httprouter.New()
webcontroller.New(r, prefix, webconf)
err := http.ListenAndServe(":8081", r)
if err != nil {
log.Error("Can't listen and serve Pixeldrain Web: %v", err)
}
}

View File

@@ -10,7 +10,7 @@ type IsAdmin struct {
// UserIsAdmin returns if the logged in user is an admin user // UserIsAdmin returns if the logged in user is an admin user
func (p *PixelAPI) UserIsAdmin() (resp IsAdmin, err error) { func (p *PixelAPI) UserIsAdmin() (resp IsAdmin, err error) {
err = p.jsonRequest("GET", p.apiEndpoint+"/admin/is_admin", &resp) err = p.jsonRequest("GET", p.apiEndpoint+"/admin/is_admin", &resp, false)
if err != nil { if err != nil {
return resp, err return resp, err
} }
@@ -31,16 +31,16 @@ type AdminGlobals struct {
// AdminGetGlobals returns if the logged in user is an admin user // AdminGetGlobals returns if the logged in user is an admin user
func (p *PixelAPI) AdminGetGlobals() (resp AdminGlobals, err error) { func (p *PixelAPI) AdminGetGlobals() (resp AdminGlobals, err error) {
if err = p.jsonRequest("GET", p.apiEndpoint+"/admin/globals", &resp); err != nil { if err = p.jsonRequest("GET", p.apiEndpoint+"/admin/globals", &resp, false); err != nil {
return resp, err return resp, err
} }
return resp, nil return resp, nil
} }
// AdminSetGlobals returns if the logged in user is an admin user // AdminSetGlobals returns if the logged in user is an admin user
func (p *PixelAPI) AdminSetGlobals(key, value string) (resp SuccessResponse, err error) { func (p *PixelAPI) AdminSetGlobals(key, value string) (err error) {
var form = url.Values{} var form = url.Values{}
form.Add("key", key) form.Add("key", key)
form.Add("value", value) form.Add("value", value)
return resp, p.form("POST", p.apiEndpoint+"/admin/globals", form, &resp, true) return p.form("POST", p.apiEndpoint+"/admin/globals", form, nil, true)
} }

View File

@@ -35,9 +35,10 @@ type FileInfo struct {
// GetFileInfo gets the FileInfo from the pixeldrain API // GetFileInfo gets the FileInfo from the pixeldrain API
func (p *PixelAPI) GetFileInfo(id string) (resp FileInfo, err error) { func (p *PixelAPI) GetFileInfo(id string) (resp FileInfo, err error) {
return resp, p.jsonRequest("GET", p.apiEndpoint+"/file/"+id+"/info", &resp) return resp, p.jsonRequest("GET", p.apiEndpoint+"/file/"+id+"/info", &resp, false)
} }
// PostFileView adds a view to a file
func (p *PixelAPI) PostFileView(id, viewtoken string) (err error) { func (p *PixelAPI) PostFileView(id, viewtoken string) (err error) {
vals := url.Values{} vals := url.Values{}
vals.Set("token", viewtoken) vals.Set("token", viewtoken)

View File

@@ -27,5 +27,5 @@ type ListFile struct {
// GetList get a List from the pixeldrain API. Errors will be available through // GetList get a List from the pixeldrain API. Errors will be available through
// List.Error. Standard error checks apply. // List.Error. Standard error checks apply.
func (p *PixelAPI) GetList(id string) (resp List, err error) { func (p *PixelAPI) GetList(id string) (resp List, err error) {
return resp, p.jsonRequest("GET", p.apiEndpoint+"/list/"+id, &resp) return resp, p.jsonRequest("GET", p.apiEndpoint+"/list/"+id, &resp, false)
} }

View File

@@ -8,12 +8,12 @@ type Recaptcha struct {
// GetRecaptcha gets the reCaptcha site key from the pixelapi server. If // GetRecaptcha gets the reCaptcha site key from the pixelapi server. If
// reCaptcha is disabled the key will be empty // reCaptcha is disabled the key will be empty
func (p *PixelAPI) GetRecaptcha() (resp Recaptcha, err error) { func (p *PixelAPI) GetRecaptcha() (resp Recaptcha, err error) {
return resp, p.jsonRequest("GET", p.apiEndpoint+"/misc/recaptcha", &resp) return resp, p.jsonRequest("GET", p.apiEndpoint+"/misc/recaptcha", &resp, false)
} }
// GetMiscViewToken requests a viewtoken from the server. The viewtoken is valid // GetMiscViewToken requests a viewtoken from the server. The viewtoken is valid
// for a limited amount of time and can be used to add views to a file. // for a limited amount of time and can be used to add views to a file.
// Viewtokens can only be requested from localhost // Viewtokens can only be requested from localhost
func (p *PixelAPI) GetMiscViewToken() (resp string, err error) { func (p *PixelAPI) GetMiscViewToken() (resp string, err error) {
return resp, p.jsonRequest("GET", p.apiEndpoint+"/misc/viewtoken", &resp) return resp, p.jsonRequest("GET", p.apiEndpoint+"/misc/viewtoken", &resp, false)
} }

View File

@@ -26,59 +26,49 @@ func New(apiEndpoint string) *PixelAPI {
return &PixelAPI{apiEndpoint: apiEndpoint} return &PixelAPI{apiEndpoint: apiEndpoint}
} }
// Error is either an error that occurred during the API request // Standard response types
// (ReqError = true), or an error that was returned from the Pixeldrain API
// (ReqError = false). The Success field is also returned by the Pixeldrain API, // Error is an error returned by the pixeldrain API. If the request failed
// but since this is struct represents an Error it will usually be false. // before it could reach the API the error will be on a different type
//
// When ReqError is true the Value field will contain a Go error message. When
// it's false it will contain a Pixeldrain API error code.
type Error struct { type Error struct {
ReqError bool Status int `json:"-"` // One of the http.Status types
Success bool `json:"success"` Success bool `json:"success"`
Value string `json:"value"` StatusCode string `json:"value"`
Message string `json:"message"` Message string `json:"message"`
Extra interface{} `json:"extra,omitempty"`
// In case of the multiple_errors code this array will be populated with
// more errors
Errors []Error `json:"errors,omitempty"`
// Metadata regarding the error
Extra map[string]interface{} `json:"extra,omitempty"`
} }
func (e Error) Error() string { return e.Value } func (e Error) Error() string { return e.StatusCode }
// SuccessResponse is a generic response the API returns when the action was func (p *PixelAPI) do(r *http.Request) (*http.Response, error) {
// successful and there is nothing interesting to report
type SuccessResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
}
func (p *PixelAPI) jsonRequest(method, url string, target interface{}) error {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return Error{
ReqError: true,
Success: false,
Value: err.Error(),
Message: err.Error(),
}
}
if p.APIKey != "" { if p.APIKey != "" {
req.SetBasicAuth("", p.APIKey) r.SetBasicAuth("", p.APIKey)
} }
if p.RealIP != "" { if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP) r.Header.Set("X-Real-IP", p.RealIP)
} }
resp, err := client.Do(req) return client.Do(r)
}
func (p *PixelAPI) jsonRequest(method, url string, target interface{}, multiErr bool) error {
req, err := http.NewRequest(method, url, nil)
if err != nil { if err != nil {
return Error{ return err
ReqError: true, }
Success: false, resp, err := p.do(req)
Value: err.Error(), if err != nil {
Message: err.Error(), return err
}
} }
defer resp.Body.Close() defer resp.Body.Close()
return parseJSONResponse(resp, target, true) return parseJSONResponse(resp, target, multiErr)
} }
func (p *PixelAPI) getString(url string) (string, error) { func (p *PixelAPI) getString(url string) (string, error) {
@@ -86,14 +76,7 @@ func (p *PixelAPI) getString(url string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if p.APIKey != "" { resp, err := p.do(req)
req.SetBasicAuth("", p.APIKey)
}
if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP)
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -110,14 +93,7 @@ func (p *PixelAPI) getRaw(url string) (io.ReadCloser, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p.APIKey != "" { resp, err := p.do(req)
req.SetBasicAuth("", p.APIKey)
}
if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP)
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -130,56 +106,29 @@ func (p *PixelAPI) form(
url string, url string,
vals url.Values, vals url.Values,
target interface{}, target interface{},
catchErrors bool, multiErr bool,
) error { ) error {
req, err := http.NewRequest(method, url, strings.NewReader(vals.Encode())) req, err := http.NewRequest(method, url, strings.NewReader(vals.Encode()))
if err != nil { if err != nil {
return Error{ return err
ReqError: true,
Success: false,
Value: err.Error(),
Message: err.Error(),
}
} }
if p.APIKey != "" {
req.SetBasicAuth("", p.APIKey)
}
if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req) resp, err := p.do(req)
if err != nil { if err != nil {
return Error{ return err
ReqError: true,
Success: false,
Value: err.Error(),
Message: err.Error(),
}
} }
defer resp.Body.Close() defer resp.Body.Close()
return parseJSONResponse(resp, target, catchErrors) return parseJSONResponse(resp, target, multiErr)
} }
func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error { func parseJSONResponse(resp *http.Response, target interface{}, multiErr bool) (err error) {
var err error
// Test for client side and server side errors // Test for client side and server side errors
if catchErrors && resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
var errResp = Error{ errResp := Error{Status: resp.StatusCode}
ReqError: false,
}
if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil { if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
log.Error("Can't decode this: %v", err) return err
return Error{
ReqError: true,
Success: false,
Value: err.Error(),
Message: err.Error(),
}
} }
return errResp return errResp
} }
@@ -191,12 +140,8 @@ func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool
if err = json.NewDecoder(resp.Body).Decode(target); err != 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 err
ReqError: true,
Success: false,
Value: err.Error(),
Message: err.Error(),
}
} }
return nil return nil
} }

View File

@@ -6,15 +6,6 @@ import (
"strconv" "strconv"
) )
// Registration is the response to the UserRegister API. The register API can
// return multiple errors, which will be stored in the Errors array. Check for
// len(Errors) == 0 to see if an error occurred
type Registration struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Errors []Error `json:"errors,omitempty"`
}
// UserRegister registers a new user on the Pixeldrain server. username and // UserRegister registers a new user on the Pixeldrain server. username and
// password are always required. email is optional, but without it you will not // password are always required. email is optional, but without it you will not
// be able to reset your password in case you forget it. captcha depends on // be able to reset your password in case you forget it. captcha depends on
@@ -24,18 +15,17 @@ type Registration struct {
// The register API can return multiple errors, which will be stored in the // The register API can return multiple errors, which will be stored in the
// Errors array. Check for len(Errors) == 0 to see if an error occurred. If err // Errors array. Check for len(Errors) == 0 to see if an error occurred. If err
// != nil it means a connection error occurred // != nil it means a connection error occurred
func (p *PixelAPI) UserRegister(username, email, password, captcha string) (resp *Registration, err error) { func (p *PixelAPI) UserRegister(username, email, password, captcha string) (err error) {
resp = &Registration{} return p.form(
var form = url.Values{} "POST", p.apiEndpoint+"/user/register",
form.Add("username", username) url.Values{
form.Add("email", email) "username": {username},
form.Add("password", password) "email": {email},
form.Add("recaptcha_response", captcha) "password": {password},
err = p.form("POST", p.apiEndpoint+"/user/register", form, resp, false) "recaptcha_response": {captcha},
if err != nil { },
return nil, err nil, true,
} )
return resp, nil
} }
// Login is the success response to the `user/login` API // Login is the success response to the `user/login` API
@@ -48,14 +38,12 @@ type Login struct {
// contain the returned API key. If saveKey is true the API key will also be // contain the returned API key. If saveKey is true the API key will also be
// saved in the client and following requests with this client will be // saved in the client and following requests with this client will be
// autenticated // autenticated
func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp *Login, err error) { func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp Login, err error) {
resp = &Login{}
var form = url.Values{} var form = url.Values{}
form.Add("username", username) form.Add("username", username)
form.Add("password", password) form.Add("password", password)
err = p.form("POST", p.apiEndpoint+"/user/login", form, resp, true) if err = p.form("POST", p.apiEndpoint+"/user/login", form, &resp, true); err != nil {
if err != nil { return resp, err
return nil, err
} }
if saveKey { if saveKey {
p.APIKey = resp.APIKey p.APIKey = resp.APIKey
@@ -71,61 +59,48 @@ type UserInfo struct {
} }
// UserInfo returns information about the logged in user. Requires an API key // UserInfo returns information about the logged in user. Requires an API key
func (p *PixelAPI) UserInfo() (resp *UserInfo, err error) { func (p *PixelAPI) UserInfo() (resp UserInfo, err error) {
resp = &UserInfo{} return resp, p.jsonRequest("GET", p.apiEndpoint+"/user", &resp, false)
err = p.jsonRequest("GET", p.apiEndpoint+"/user", resp)
if err != nil {
return nil, err
}
return resp, nil
} }
// 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) (err error) { func (p *PixelAPI) UserSessionDestroy(key string) (err error) {
return p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", nil) return p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", nil, false)
} }
// UserFiles is a list of files uploaded by a user
type UserFiles struct { type UserFiles struct {
Success bool `json:"success"` Success bool `json:"success"`
Files []FileInfo `json:"files"` Files []FileInfo `json:"files"`
} }
func (p *PixelAPI) UserFiles(page, limit int) (resp *UserFiles, err error) { // UserFiles gets files uploaded by a user
resp = &UserFiles{Files: make([]FileInfo, 0)} func (p *PixelAPI) UserFiles(page, limit int) (resp UserFiles, err error) {
err = p.jsonRequest( return resp, p.jsonRequest(
"GET", "GET", fmt.Sprintf("%s/user/files?page=%d&limit=%d", p.apiEndpoint, page, limit), &resp, false,
fmt.Sprintf("%s/user/files?page=%d&limit=%d", p.apiEndpoint, page, limit),
resp,
) )
if err != nil {
return nil, err
}
return resp, nil
} }
// UserLists is a list of lists created by a user
type UserLists struct { type UserLists struct {
Success bool `json:"success"` Success bool `json:"success"`
Lists []List `json:"lists"` Lists []List `json:"lists"`
} }
func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) { // UserLists gets lists created by a user
resp = &UserLists{Lists: make([]List, 0)} func (p *PixelAPI) UserLists(page, limit int) (resp UserLists, err error) {
if err = p.jsonRequest( return resp, p.jsonRequest(
"GET", "GET", fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit), &resp, false,
fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit), )
resp,
); err != nil {
return nil, err
}
return resp, nil
} }
// UserPasswordSet changes the user's password
func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (err error) { func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (err error) {
var form = url.Values{} return p.form(
form.Add("old_password", oldPW) "PUT", p.apiEndpoint+"/user/password",
form.Add("new_password", newPW) url.Values{"old_password": {oldPW}, "new_password": {newPW}}, nil, true,
return p.form("PUT", p.apiEndpoint+"/user/password", form, nil, true) )
} }
// UserEmailReset starts the e-mail change process. An email will be sent to the // UserEmailReset starts the e-mail change process. An email will be sent to the
@@ -141,9 +116,10 @@ func (p *PixelAPI) UserEmailReset(email string, delete bool) (err error) {
// UserEmailResetConfirm finishes process of changing a user's e-mail address // UserEmailResetConfirm finishes process of changing a user's e-mail address
func (p *PixelAPI) UserEmailResetConfirm(key string) (err error) { func (p *PixelAPI) UserEmailResetConfirm(key string) (err error) {
var form = url.Values{} return p.form(
form.Add("key", key) "PUT", p.apiEndpoint+"/user/email_reset_confirm",
return p.form("PUT", p.apiEndpoint+"/user/email_reset_cofirm", form, nil, true) url.Values{"key": {key}}, nil, true,
)
} }
// UserPasswordReset starts the password reset process. An email will be sent // UserPasswordReset starts the password reset process. An email will be sent

View File

@@ -0,0 +1,45 @@
Vue.component("vform", {
template: `
<h2>TEST FORM</h2>
<form class="highlight_dark" method="POST">
<table class="form">
<tbody>
<tr class="form">
<td>Old Password</td>
<td>
<input id="input_old_password" name="old_password" value="" type="password" autocomplete="current-password" class="form_input">
</td>
</tr>
<tr class="form"></tr>
<td>New Password</td>
<td>
<input id="input_new_password1" name="new_password1" value="" type="password" autocomplete="new-password" class="form_input">
</td>
</tr>
<tr class="form">
<td>New Password again</td>
<td>
<input id="input_new_password2" name="new_password2" value="" type="password" autocomplete="new-password" class="form_input">
</td>
</tr>
<tr class="form">
<td colspan="2">
we need you to repeat your password so you won't be locked out of your account if you make a typing error
</td>
</tr>
<tr class="form">
<td colspan="2" style="text-align: right;">
<input type="submit" value="Submit" class="button_highlight" style="float: right;">
</td>
</tr>
</tbody>
</table>
</form>
`
})
Vue.component("form_field", {
template: `
`
})

View File

@@ -0,0 +1,3 @@
const appTestForm = new Vue({
el: "#test_form"
})

11965
res/static/script/vue.js Normal file

File diff suppressed because it is too large Load Diff

6
res/static/script/vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -49,15 +49,21 @@
let breadcrumbs = document.querySelector("#nav_bar > .breadcrumbs") let breadcrumbs = document.querySelector("#nav_bar > .breadcrumbs")
if (window.location.href.endsWith("?files")) { let hashChange = () => {
breadcrumbs.value = "/{{.Username}}/Files" if (window.location.hash === "#files") {
fm.getUserFiles() breadcrumbs.value = "/{{.Username}}/Files"
} else if (window.location.href.endsWith("?lists")) { fm.getUserFiles()
breadcrumbs.value = "/{{.Username}}/Lists" } else if (window.location.hash === "#lists") {
fm.getUserLists() breadcrumbs.value = "/{{.Username}}/Lists"
} else { fm.getUserLists()
alert("invalid file manager type") } else {
alert("invalid file manager type")
}
} }
hashChange()
window.addEventListener("hashchange", hashChange)
}) })
</script> </script>
{{template "analytics"}} {{template "analytics"}}

View File

@@ -0,0 +1,57 @@
{{define "user_settings_vue_test"}}<!DOCTYPE html>
<html lang="en">
<head>
{{template "meta_tags" .Username}}
{{template "user_style" .}}
<script>var apiEndpoint = '{{.APIEndpoint}}';</script>
</head>
<body>
{{template "page_top" .}}
<h1>{{.Title}}</h1>
<div class="page_content"><div class="limit_width">
<h2>TEST FORM</h2>
<form id="test_form" class="highlight_dark" method="POST">
<table class="form">
<tr class="form">
<td>Old Password</td>
<td>
<input id="input_old_password" name="old_password" value="" type="password" autocomplete="current-password" class="form_input">
</td>
</tr>
<tr class="form"></tr>
<td>New Password</td>
<td>
<input id="input_new_password1" name="new_password1" value="" type="password" autocomplete="new-password" class="form_input">
</td>
</tr>
<tr class="form">
<td>New Password again</td>
<td>
<input id="input_new_password2" name="new_password2" value="" type="password" autocomplete="new-password" class="form_input">
</td>
</tr>
<tr class="form">
<td colspan="2">
we need you to repeat your password so you won't be locked out of your account if you make a typing error
</td>
</tr>
<tr class="form">
<td colspan="2" style="text-align: right;">
<input type="submit" value="Submit" class="button_highlight" style="float: right;">
</td>
</tr>
</table>
</form>
{{template "form" .Other.PasswordForm}}
{{template "form" .Other.EmailForm}}
{{template "form" .Other.UsernameForm}}
</div></div>
{{template "page_bottom" .}}
{{template "analytics"}}
{{template "vue"}}
</body>
</html>
{{end}}

View File

@@ -52,17 +52,17 @@
{{end}} {{end}}
</td> </td>
{{end}} {{end}}
{{if or (ne $field.Description "") (eq $field.Separator true)}}
<tr class="form">
<td colspan="2">
{{$field.Description}}
{{if eq $field.Separator true}}
<hr/>
{{end}}
</td>
</tr>
{{end}}
</tr> </tr>
{{if or (ne $field.Description "") (eq $field.Separator true)}}
<tr class="form">
<td colspan="2">
{{$field.Description}}
{{if eq $field.Separator true}}
<hr/>
{{end}}
</td>
</tr>
{{end}}
{{end}} {{end}}
<tr class="form"> <tr class="form">
{{if eq .BackLink ""}} {{if eq .BackLink ""}}

View File

@@ -1,10 +0,0 @@
{{define "tpl_file_button"}}
<template id="tpl_file_button">
<a class="file_button" target="_blank">
<img src="/api/file/xWwGvj9f/thumbnail?width=80&height=80" alt="24000.jpg" />
<span style="color: var(--highlight_color);">24000.jpg</span>
<br/>
2020-01-20 10:06:56
</a>
</template>
{{end}}

View File

@@ -18,3 +18,8 @@ pixeldrain also supports previews for images, videos, audio, PDFs and much more.
<meta property="og:image" content="/res/img/pixeldrain_big.png" /> <meta property="og:image" content="/res/img/pixeldrain_big.png" />
<meta property="og:image:type" content="image/png" /> <meta property="og:image:type" content="image/png" />
{{end}} {{end}}
{{define "vue"}}{{if debugMode}}
<script src="/res/script/vue.js"></script>
{{else}}
<script src="/res/script/vue.min.js"></script>
{{end}}{{end}}

View File

@@ -4,8 +4,8 @@
<a href="/">Home</a> <a href="/">Home</a>
<hr /> <hr />
{{if .Authenticated}}<a href="/user">{{.Username}}</a> {{if .Authenticated}}<a href="/user">{{.Username}}</a>
<a href="/user/filemanager?files">My Files</a> <a href="/user/filemanager#files">My Files</a>
<a href="/user/filemanager?lists">My Lists</a> <a href="/user/filemanager#lists">My Lists</a>
<a href="/logout">Log out</a> <a href="/logout">Log out</a>
{{else}} {{else}}
<a href="/login">Login</a> <a href="/login">Login</a>

View File

@@ -69,7 +69,7 @@ func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f
} }
// Value changed, try to update global setting // Value changed, try to update global setting
if _, err = td.PixelAPI.AdminSetGlobals(v.Name, v.EnteredValue); err != nil { if err = td.PixelAPI.AdminSetGlobals(v.Name, v.EnteredValue); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { if apiErr, ok := err.(pixelapi.Error); ok {
f.SubmitMessages = append(f.SubmitMessages, template.HTML(apiErr.Message)) f.SubmitMessages = append(f.SubmitMessages, template.HTML(apiErr.Message))
} else { } else {

View File

@@ -130,11 +130,14 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request,
var templateData = wc.newTemplateData(w, r) var templateData = wc.newTemplateData(w, r)
var list, err = templateData.PixelAPI.GetList(p.ByName("id")) var list, err = templateData.PixelAPI.GetList(p.ByName("id"))
if err != nil { if err != nil {
if err, ok := err.(pixelapi.Error); ok && err.ReqError { if err, ok := err.(pixelapi.Error); ok && err.Status == http.StatusNotFound {
log.Error("API request error occurred: %s", err.Value) w.WriteHeader(http.StatusNotFound)
wc.templates.Get().ExecuteTemplate(w, "list_not_found", templateData)
} else {
log.Error("API request error occurred: %s", err)
w.WriteHeader(http.StatusInternalServerError)
wc.templates.Get().ExecuteTemplate(w, "500", templateData)
} }
w.WriteHeader(http.StatusNotFound)
wc.templates.Get().ExecuteTemplate(w, "list_not_found", templateData)
return return
} }

View File

@@ -27,6 +27,7 @@ func (wc *WebController) serveLogout(
} }
func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form) { func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form) {
var err error
// This only runs on the first request // This only runs on the first request
if wc.captchaSiteKey == "" { if wc.captchaSiteKey == "" {
capt, err := td.PixelAPI.GetRecaptcha() capt, err := td.PixelAPI.GetRecaptcha()
@@ -58,7 +59,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form
Separator: true, Separator: true,
Type: FieldTypeUsername, Type: FieldTypeUsername,
}, { }, {
Name: "e-mail", Name: "email",
Label: "E-mail address", Label: "E-mail address",
Description: "not required. your e-mail address will only be " + Description: "not required. your e-mail address will only be " +
"used for password resets and important account " + "used for password resets and important account " +
@@ -66,7 +67,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form
Separator: true, Separator: true,
Type: FieldTypeEmail, Type: FieldTypeEmail,
}, { }, {
Name: "password1", Name: "password",
Label: "Password", Label: "Password",
Type: FieldTypeNewPassword, Type: FieldTypeNewPassword,
}, { }, {
@@ -79,10 +80,11 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form
Type: FieldTypeNewPassword, Type: FieldTypeNewPassword,
}, { }, {
Name: "recaptcha_response", Name: "recaptcha_response",
Label: "Turing test (click the white box)", Label: "reCaptcha",
Description: "the reCaptcha turing test verifies that you " + Description: "the reCaptcha turing test verifies that you " +
"are not an evil robot that is trying to flood the " + "are not an evil robot that is trying to flood the " +
"website with fake accounts", "website with fake accounts. Please click the white box " +
"to prove that you're not a robot",
Separator: true, Separator: true,
Type: FieldTypeCaptcha, Type: FieldTypeCaptcha,
CaptchaSiteKey: wc.captchaKey(), CaptchaSiteKey: wc.captchaKey(),
@@ -94,31 +96,21 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form
} }
if f.ReadInput(r) { if f.ReadInput(r) {
if f.FieldVal("password1") != f.FieldVal("password2") { if f.FieldVal("password") != f.FieldVal("password2") {
f.SubmitMessages = []template.HTML{ f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " + "Password verification failed. Please enter the same " +
"password in both password fields"} "password in both password fields"}
return f return f
} }
log.Debug("capt: %s", f.FieldVal("recaptcha_response")) log.Debug("capt: %s", f.FieldVal("recaptcha_response"))
resp, err := td.PixelAPI.UserRegister(
if err = td.PixelAPI.UserRegister(
f.FieldVal("username"), f.FieldVal("username"),
f.FieldVal("e-mail"), f.FieldVal("email"),
f.FieldVal("password1"), f.FieldVal("password"),
f.FieldVal("recaptcha_response"), f.FieldVal("recaptcha_response"),
) ); err != nil {
if err != nil { formAPIError(err, &f)
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 if len(resp.Errors) != 0 {
// Registration errors occurred
for _, rerr := range resp.Errors {
f.SubmitMessages = append(f.SubmitMessages, template.HTML(rerr.Message))
}
} else { } else {
// Request was a success // Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true
@@ -162,12 +154,7 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f Form) {
if f.ReadInput(r) { if f.ReadInput(r) {
loginResp, err := td.PixelAPI.UserLogin(f.FieldVal("username"), f.FieldVal("password"), false) loginResp, err := td.PixelAPI.UserLogin(f.FieldVal("username"), f.FieldVal("password"), false)
if err != nil { if err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { formAPIError(err, &f)
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
log.Debug("key %s", loginResp.APIKey) log.Debug("key %s", loginResp.APIKey)
// Request was a success // Request was a success
@@ -223,13 +210,11 @@ func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f
} }
if f.ReadInput(r) { if f.ReadInput(r) {
if err := td.PixelAPI.UserPasswordReset(f.FieldVal("email"), f.FieldVal("recaptcha_response")); err != nil { if err := td.PixelAPI.UserPasswordReset(
if apiErr, ok := err.(pixelapi.Error); ok { f.FieldVal("email"),
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)} f.FieldVal("recaptcha_response"),
} else { ); err != nil {
log.Error("%s", err) formAPIError(err, &f)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
f.SubmitSuccess = true f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{ f.SubmitMessages = []template.HTML{
@@ -247,12 +232,12 @@ func (wc *WebController) passwordResetConfirmForm(td *TemplateData, r *http.Requ
Title: td.Title, Title: td.Title,
Fields: []Field{ Fields: []Field{
{ {
Name: "password1", Name: "new_password",
Label: "password", Label: "Password",
Type: FieldTypeNewPassword, Type: FieldTypeNewPassword,
}, { }, {
Name: "password2", Name: "new_password2",
Label: "password again", Label: "Password again",
Description: "you need to enter your password twice so we " + Description: "you need to enter your password twice so we " +
"can verify that no typing errors were made, which would " + "can verify that no typing errors were made, which would " +
"prevent you from logging into your new account", "prevent you from logging into your new account",
@@ -271,23 +256,20 @@ func (wc *WebController) passwordResetConfirmForm(td *TemplateData, r *http.Requ
} }
if f.ReadInput(r) { if f.ReadInput(r) {
if f.FieldVal("password1") != f.FieldVal("password2") { if f.FieldVal("new_password") != f.FieldVal("new_password2") {
f.SubmitMessages = []template.HTML{ f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " + "Password verification failed. Please enter the same " +
"password in both password fields"} "password in both password fields"}
return f return f
} }
if err := td.PixelAPI.UserPasswordResetConfirm(resetKey, f.FieldVal("password1")); err != nil { if err := td.PixelAPI.UserPasswordResetConfirm(resetKey, f.FieldVal("new_password")); err != nil {
if err.Error() == "not_found" { formAPIError(err, &f)
f.SubmitMessages = []template.HTML{template.HTML("Password reset key not found")}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
f.SubmitSuccess = true f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success! You can now log in with your new password"} f.SubmitMessages = []template.HTML{
`Success! You can now <a href="/login">log in</a> with your new password`,
}
} }
} }
return f return f

View File

@@ -1,6 +1,7 @@
package webcontroller package webcontroller
import ( import (
"fmt"
"html/template" "html/template"
"net/http" "net/http"
@@ -9,6 +10,44 @@ import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
) )
// formAPIError makes it easier to display errors returned by the pixeldrain
// API. TO make use of this function the form fields should be named exactly the
// same as the API parameters
func formAPIError(err error, f *Form) {
fieldLabel := func(name string) string {
for _, v := range f.Fields {
if v.Name == name {
return v.Label
}
}
return name
}
if err, ok := err.(pixelapi.Error); ok {
if err.StatusCode == "multiple_errors" {
for _, err := range err.Errors {
// Modify the message to make it more user-friendly
if err.StatusCode == "string_out_of_range" {
err.Message = fmt.Sprintf(
"%s is too long or too short. Should be between %v and %v characters. Current length: %v",
fieldLabel(err.Extra["field"].(string)),
err.Extra["min_len"],
err.Extra["max_len"],
err.Extra["len"],
)
}
f.SubmitMessages = append(f.SubmitMessages, template.HTML(err.Message))
}
} else {
f.SubmitMessages = append(f.SubmitMessages, template.HTML(err.Message))
}
} else {
log.Error("Error submitting form: %s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
}
func (wc *WebController) serveUserSettings( func (wc *WebController) serveUserSettings(
w http.ResponseWriter, w http.ResponseWriter,
r *http.Request, r *http.Request,
@@ -44,7 +83,7 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f Form
Label: "Old Password", Label: "Old Password",
Type: FieldTypeCurrentPassword, Type: FieldTypeCurrentPassword,
}, { }, {
Name: "new_password1", Name: "new_password",
Label: "New Password", Label: "New Password",
Type: FieldTypeNewPassword, Type: FieldTypeNewPassword,
}, { }, {
@@ -60,7 +99,7 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f Form
} }
if f.ReadInput(r) { if f.ReadInput(r) {
if f.FieldVal("new_password1") != f.FieldVal("new_password2") { if f.FieldVal("new_password") != f.FieldVal("new_password2") {
f.SubmitMessages = []template.HTML{ f.SubmitMessages = []template.HTML{
"Password verification failed. Please enter the same " + "Password verification failed. Please enter the same " +
"password in both new password fields"} "password in both new password fields"}
@@ -71,14 +110,9 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f Form
// form // form
if err := td.PixelAPI.UserPasswordSet( if err := td.PixelAPI.UserPasswordSet(
f.FieldVal("old_password"), f.FieldVal("old_password"),
f.FieldVal("new_password1"), f.FieldVal("new_password"),
); err != nil { ); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { formAPIError(err, &f)
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
// Request was a success // Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true
@@ -111,12 +145,7 @@ func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f Form) {
f.FieldVal("new_email"), f.FieldVal("new_email"),
false, false,
); err != nil { ); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { formAPIError(err, &f)
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
// Request was a success // Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true
@@ -131,18 +160,18 @@ func (wc *WebController) serveEmailConfirm(
r *http.Request, r *http.Request,
p httprouter.Params, p httprouter.Params,
) { ) {
var err error
var status string var status string
if key, err := wc.getAPIKey(r); err == nil {
api := pixelapi.New(wc.apiURLInternal) api := pixelapi.New(wc.apiURLInternal)
api.APIKey = key err = api.UserEmailResetConfirm(r.FormValue("key"))
err = api.UserEmailResetConfirm(r.FormValue("key")) if err != nil && err.Error() == "not_found" {
if err != nil && err.Error() == "not_found" { status = "not_found"
status = "not_found" } else if err != nil {
} else if err != nil { log.Error("E-mail reset fail: %s", err)
status = "internal_error" status = "internal_error"
} else { } else {
status = "success" status = "success"
}
} }
td := wc.newTemplateData(w, r) td := wc.newTemplateData(w, r)
@@ -171,12 +200,7 @@ func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f Form
if f.ReadInput(r) { if f.ReadInput(r) {
if err := td.PixelAPI.UserSetUsername(f.FieldVal("new_username")); err != nil { if err := td.PixelAPI.UserSetUsername(f.FieldVal("new_username")); err != nil {
if apiErr, ok := err.(pixelapi.Error); ok { formAPIError(err, &f)
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
} else {
log.Error("%s", err)
f.SubmitMessages = []template.HTML{"Internal Server Error"}
}
} else { } else {
// Request was a success // Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true