UX: Refactor Library UI
This commit is contained in:
parent
0925d7179c
commit
43714c00d5
|
@ -32,7 +32,7 @@ import Event from "pubsub-js";
|
||||||
|
|
||||||
class Log {
|
class Log {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cap = 100;
|
this.cap = 150;
|
||||||
this.created = new Date;
|
this.created = new Date;
|
||||||
this.logs = [
|
this.logs = [
|
||||||
/* EXAMPLE LOG MESSAGE
|
/* EXAMPLE LOG MESSAGE
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://docs.photoprism.org/developer-guide/
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {$gettext} from "common/vm";
|
import {$gettext} from "common/vm";
|
||||||
|
import {config} from "../session";
|
||||||
|
|
||||||
export class Account extends RestModel {
|
export class Account extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
|
@ -57,7 +58,7 @@ export class Account extends RestModel {
|
||||||
SyncDate: null,
|
SyncDate: null,
|
||||||
SyncFilenames: true,
|
SyncFilenames: true,
|
||||||
SyncUpload: false,
|
SyncUpload: false,
|
||||||
SyncDownload: true,
|
SyncDownload: !config.get("readonly"),
|
||||||
SyncRaw: true,
|
SyncRaw: true,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
|
|
|
@ -8,35 +8,17 @@
|
||||||
slider-color="secondary-dark"
|
slider-color="secondary-dark"
|
||||||
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
||||||
>
|
>
|
||||||
<v-tab id="tab-index" ripple @click="changePath('/library')">
|
<v-tab v-for="(tab, index) in tabs" :key="index" :id="'tab-' + tab.name" :class="tab.class" ripple
|
||||||
<translate key="Index">Index</translate>
|
@click="changePath(tab.path)">
|
||||||
</v-tab>
|
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="tab.label">{{ tab.icon }}</v-icon>
|
||||||
|
|
||||||
<v-tab id="tab-import" :disabled="readonly || !$config.feature('import')" ripple
|
|
||||||
@click="changePath('/library/import')">
|
|
||||||
<template v-if="config.settings.import.move">
|
|
||||||
<translate key="Move">Move</translate>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<translate key="Copy">Copy</translate>
|
<v-icon :size="18" left>{{ tab.icon }}</v-icon> {{ tab.label }}
|
||||||
</template>
|
</template>
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
|
||||||
<v-tab id="tab-logs" ripple @click="changePath('/library/logs')" v-if="$config.feature('logs')">
|
|
||||||
<translate key="Logs">Logs</translate>
|
|
||||||
</v-tab>
|
|
||||||
|
|
||||||
<v-tabs-items touchless>
|
<v-tabs-items touchless>
|
||||||
<v-tab-item lazy>
|
<v-tab-item lazy v-for="(tab, index) in tabs" :key="index">
|
||||||
<p-tab-index></p-tab-index>
|
<component v-bind:is="tab.component"></component>
|
||||||
</v-tab-item>
|
|
||||||
|
|
||||||
<v-tab-item :disabled="readonly" lazy>
|
|
||||||
<p-tab-import></p-tab-import>
|
|
||||||
</v-tab-item>
|
|
||||||
|
|
||||||
<v-tab-item v-if="$config.feature('logs')">
|
|
||||||
<p-tab-logs></p-tab-logs>
|
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
@ -48,10 +30,21 @@ import tabImport from "pages/library/import.vue";
|
||||||
import tabIndex from "pages/library/index.vue";
|
import tabIndex from "pages/library/index.vue";
|
||||||
import tabLogs from "pages/library/logs.vue";
|
import tabLogs from "pages/library/logs.vue";
|
||||||
|
|
||||||
|
function initTabs(flag, tabs) {
|
||||||
|
let i = 0;
|
||||||
|
while(i < tabs.length) {
|
||||||
|
if(!tabs[i][flag]) {
|
||||||
|
tabs.splice(i,1);
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'p-page-library',
|
name: 'p-page-library',
|
||||||
props: {
|
props: {
|
||||||
tab: Number
|
tab: String,
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
'p-tab-index': tabIndex,
|
'p-tab-index': tabIndex,
|
||||||
|
@ -59,10 +52,66 @@ export default {
|
||||||
'p-tab-logs': tabLogs,
|
'p-tab-logs': tabLogs,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
const config = this.$config.values;
|
||||||
|
const isDemo = this.$config.get("demo");
|
||||||
|
const isPublic = this.$config.get("public");
|
||||||
|
const isReadOnly = this.$config.get("readonly");
|
||||||
|
const canImport = this.$config.feature('import') && !isReadOnly;
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
'name': 'library-index',
|
||||||
|
'component': tabIndex,
|
||||||
|
'label': this.$gettext('Index'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/library',
|
||||||
|
'icon': 'update',
|
||||||
|
'readonly': true,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'library-import',
|
||||||
|
'component': tabImport,
|
||||||
|
'label': this.$gettext('Import'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/library/import',
|
||||||
|
'icon': 'perm_media',
|
||||||
|
'readonly': false,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'library-logs',
|
||||||
|
'component': tabLogs,
|
||||||
|
'label': this.$gettext('Logs'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/library/logs',
|
||||||
|
'icon': 'notes',
|
||||||
|
'readonly': true,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if(isDemo) {
|
||||||
|
initTabs("demo", tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!canImport) {
|
||||||
|
initTabs("readonly", tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let active = 0;
|
||||||
|
|
||||||
|
if (typeof this.tab === 'string' && this.tab !== '') {
|
||||||
|
active = tabs.findIndex((t) => t.name === this.tab);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config: this.$config.values,
|
tabs: tabs,
|
||||||
readonly: this.$config.get("readonly"),
|
demo: isDemo,
|
||||||
active: this.tab,
|
public: isPublic,
|
||||||
|
config: config,
|
||||||
|
readonly: isReadOnly,
|
||||||
|
active: active,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -10,8 +10,11 @@
|
||||||
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
||||||
>
|
>
|
||||||
<v-tab v-for="(tab, index) in tabs" :key="index" :id="'tab-' + tab.name" :class="tab.class" ripple
|
<v-tab v-for="(tab, index) in tabs" :key="index" :id="'tab-' + tab.name" :class="tab.class" ripple
|
||||||
@click="changePath(tab.path)" :title="tab.label">
|
@click="changePath(tab.path)">
|
||||||
<v-icon>{{ tab.icon }}</v-icon>
|
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="tab.label">{{ tab.icon }}</v-icon>
|
||||||
|
<template v-else>
|
||||||
|
<v-icon :size="18" left>{{ tab.icon }}</v-icon> {{ tab.label }}
|
||||||
|
</template>
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
|
||||||
<v-tabs-items touchless>
|
<v-tabs-items touchless>
|
||||||
|
@ -28,52 +31,8 @@ import tabGeneral from "pages/settings/general.vue";
|
||||||
import tabLibrary from "pages/settings/library.vue";
|
import tabLibrary from "pages/settings/library.vue";
|
||||||
import tabSync from "pages/settings/sync.vue";
|
import tabSync from "pages/settings/sync.vue";
|
||||||
import tabAccount from "pages/settings/account.vue";
|
import tabAccount from "pages/settings/account.vue";
|
||||||
import {$gettext} from "common/vm";
|
|
||||||
|
|
||||||
const tabs = [
|
function initTabs(flag, tabs) {
|
||||||
{
|
|
||||||
'name': 'settings-general',
|
|
||||||
'component': tabGeneral,
|
|
||||||
'label': $gettext('General'),
|
|
||||||
'class': '',
|
|
||||||
'path': '/settings',
|
|
||||||
'icon': 'tv',
|
|
||||||
'public': true,
|
|
||||||
'demo': true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'settings-library',
|
|
||||||
'component': tabLibrary,
|
|
||||||
'label': $gettext('Library'),
|
|
||||||
'class': '',
|
|
||||||
'path': '/settings/library',
|
|
||||||
'icon': 'camera_roll',
|
|
||||||
'public': true,
|
|
||||||
'demo': true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'settings-sync',
|
|
||||||
'component': tabSync,
|
|
||||||
'label': $gettext('Sync'),
|
|
||||||
'class': '',
|
|
||||||
'path': '/settings/sync',
|
|
||||||
'icon': 'sync_alt',
|
|
||||||
'public': false,
|
|
||||||
'demo': true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'settings-account',
|
|
||||||
'component': tabAccount,
|
|
||||||
'label': $gettext('Account'),
|
|
||||||
'class': '',
|
|
||||||
'path': '/settings/account',
|
|
||||||
'icon': 'vpn_key',
|
|
||||||
'public': false,
|
|
||||||
'demo': true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function initTabs(flag) {
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while(i < tabs.length) {
|
while(i < tabs.length) {
|
||||||
if(!tabs[i][flag]) {
|
if(!tabs[i][flag]) {
|
||||||
|
@ -98,11 +57,53 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
const isDemo = this.$config.get("demo");
|
const isDemo = this.$config.get("demo");
|
||||||
const isPublic = this.$config.get("public");
|
const isPublic = this.$config.get("public");
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
'name': 'settings-general',
|
||||||
|
'component': tabGeneral,
|
||||||
|
'label': this.$gettext('General'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/settings',
|
||||||
|
'icon': 'tv',
|
||||||
|
'public': true,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'settings-library',
|
||||||
|
'component': tabLibrary,
|
||||||
|
'label': this.$gettext('Library'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/settings/library',
|
||||||
|
'icon': 'camera_roll',
|
||||||
|
'public': true,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'settings-sync',
|
||||||
|
'component': tabSync,
|
||||||
|
'label': this.$gettext('Sync'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/settings/sync',
|
||||||
|
'icon': 'sync_alt',
|
||||||
|
'public': true,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'settings-account',
|
||||||
|
'component': tabAccount,
|
||||||
|
'label': this.$gettext('Account'),
|
||||||
|
'class': '',
|
||||||
|
'path': '/settings/account',
|
||||||
|
'icon': 'person',
|
||||||
|
'public': false,
|
||||||
|
'demo': true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
if(isDemo) {
|
if(isDemo) {
|
||||||
initTabs("demo");
|
initTabs("demo", tabs);
|
||||||
} else if(isPublic) {
|
} else if(isPublic) {
|
||||||
initTabs("public");
|
initTabs("public", tabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = 0;
|
let active = 0;
|
||||||
|
|
|
@ -190,7 +190,7 @@
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:label="$gettext('Import')"
|
:label="$gettext('Import')"
|
||||||
:hint="$gettext('Imported files will be sorted by date and given a unique name.')"
|
:hint="$gettext('Imported files will be sorted by date and given a unique name.')"
|
||||||
prepend-icon="create_new_folder"
|
prepend-icon="perm_media"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
>
|
>
|
||||||
</v-checkbox>
|
</v-checkbox>
|
||||||
|
|
|
@ -249,25 +249,25 @@ export default [
|
||||||
meta: {title: $gettext("People"), auth: true},
|
meta: {title: $gettext("People"), auth: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "library_logs",
|
name: "library",
|
||||||
path: "/library/logs",
|
path: "/library",
|
||||||
component: Library,
|
component: Library,
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
||||||
props: {tab: 2},
|
props: {tab: "library-index"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "library_import",
|
name: "library_import",
|
||||||
path: "/library/import",
|
path: "/library/import",
|
||||||
component: Library,
|
component: Library,
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
||||||
props: {tab: 1},
|
props: {tab: "library-import"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "library",
|
name: "library_logs",
|
||||||
path: "/library",
|
path: "/library/logs",
|
||||||
component: Library,
|
component: Library,
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
||||||
props: {tab: 0},
|
props: {tab: "library-logs"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings",
|
name: "settings",
|
||||||
|
|
|
@ -31,6 +31,13 @@ func GetAccounts(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
c.JSON(http.StatusOK, entity.Accounts{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var f form.AccountSearch
|
var f form.AccountSearch
|
||||||
|
|
||||||
err := c.MustBindWith(&f, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
@ -68,6 +75,13 @@ func GetAccount(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortUnauthorized(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id := ParseUint(c.Param("id"))
|
id := ParseUint(c.Param("id"))
|
||||||
|
|
||||||
if m, err := query.AccountByID(id); err == nil {
|
if m, err := query.AccountByID(id); err == nil {
|
||||||
|
@ -91,6 +105,13 @@ func GetAccountFolders(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortUnauthorized(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
id := ParseUint(c.Param("id"))
|
id := ParseUint(c.Param("id"))
|
||||||
cache := service.Cache()
|
cache := service.Cache()
|
||||||
|
@ -191,6 +212,13 @@ func CreateAccount(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortUnauthorized(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var f form.Account
|
var f form.Account
|
||||||
|
|
||||||
if err := c.BindJSON(&f); err != nil {
|
if err := c.BindJSON(&f); err != nil {
|
||||||
|
@ -206,8 +234,6 @@ func CreateAccount(router *gin.RouterGroup) {
|
||||||
|
|
||||||
m, err := entity.CreateAccount(f)
|
m, err := entity.CreateAccount(f)
|
||||||
|
|
||||||
log.Debugf("account: creating %+v %+v", f, m)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
AbortBadRequest(c)
|
AbortBadRequest(c)
|
||||||
|
@ -233,6 +259,13 @@ func UpdateAccount(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortUnauthorized(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id := ParseUint(c.Param("id"))
|
id := ParseUint(c.Param("id"))
|
||||||
|
|
||||||
m, err := query.AccountByID(id)
|
m, err := query.AccountByID(id)
|
||||||
|
@ -295,6 +328,13 @@ func DeleteAccount(router *gin.RouterGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf := service.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortUnauthorized(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id := ParseUint(c.Param("id"))
|
id := ParseUint(c.Param("id"))
|
||||||
|
|
||||||
m, err := query.AccountByID(id)
|
m, err := query.AccountByID(id)
|
||||||
|
|
|
@ -12,9 +12,9 @@ import (
|
||||||
|
|
||||||
func TestGetAccounts(t *testing.T) {
|
func TestGetAccounts(t *testing.T) {
|
||||||
t.Run("successful request", func(t *testing.T) {
|
t.Run("successful request", func(t *testing.T) {
|
||||||
app, router, _ := NewApiTest()
|
app, router, _, sess := NewAdminApiTest()
|
||||||
GetAccounts(router)
|
GetAccounts(router)
|
||||||
r := PerformRequest(app, "GET", "/api/v1/accounts?count=10")
|
r := AuthenticatedRequest(app, "GET", "/api/v1/accounts?count=10", sess)
|
||||||
val := gjson.Get(r.Body.String(), "#(AccName=\"Test Account\").AccURL")
|
val := gjson.Get(r.Body.String(), "#(AccName=\"Test Account\").AccURL")
|
||||||
count := gjson.Get(r.Body.String(), "#")
|
count := gjson.Get(r.Body.String(), "#")
|
||||||
assert.LessOrEqual(t, int64(1), count.Int())
|
assert.LessOrEqual(t, int64(1), count.Int())
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewApiTest returns new API test helper
|
// NewApiTest returns new API test helper.
|
||||||
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config) {
|
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config) {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
app = gin.New()
|
app = gin.New()
|
||||||
|
@ -21,6 +21,20 @@ func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config
|
||||||
return app, router, service.Config()
|
return app, router, service.Config()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewApiTest returns new API test helper with authenticated admin session.
|
||||||
|
func NewAdminApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config, sessId string) {
|
||||||
|
app = gin.New()
|
||||||
|
router = app.Group("/api/v1")
|
||||||
|
CreateSession(router)
|
||||||
|
reader := strings.NewReader(`{"username": "admin", "password": "photoprism"}`)
|
||||||
|
req, _ := http.NewRequest("POST", "/api/v1/session", reader)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
app.ServeHTTP(w, req)
|
||||||
|
sessId = w.Header().Get("X-Session-ID")
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
return app, router, service.Config(), sessId
|
||||||
|
}
|
||||||
|
|
||||||
// Performs API request with empty request body.
|
// Performs API request with empty request body.
|
||||||
// See https://medium.com/@craigchilds94/testing-gin-json-responses-1f258ce3b0b1
|
// See https://medium.com/@craigchilds94/testing-gin-json-responses-1f258ce3b0b1
|
||||||
func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
|
func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
|
||||||
|
@ -30,6 +44,15 @@ func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecor
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Performs authenticated API request with empty request body.
|
||||||
|
func AuthenticatedRequest(r http.Handler, method, path, sess string) *httptest.ResponseRecorder {
|
||||||
|
req, _ := http.NewRequest(method, path, nil)
|
||||||
|
req.Header.Add("X-Session-ID", sess)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
// Performs API request including request body as string.
|
// Performs API request including request body as string.
|
||||||
func PerformRequestWithBody(r http.Handler, method, path, body string) *httptest.ResponseRecorder {
|
func PerformRequestWithBody(r http.Handler, method, path, body string) *httptest.ResponseRecorder {
|
||||||
reader := strings.NewReader(body)
|
reader := strings.NewReader(body)
|
||||||
|
|
|
@ -16,7 +16,7 @@ func ChangePassword(router *gin.RouterGroup) {
|
||||||
router.PUT("/users/:uid/password", func(c *gin.Context) {
|
router.PUT("/users/:uid/password", func(c *gin.Context) {
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
|
|
||||||
if conf.Public() {
|
if conf.Public() || conf.DisableSettings() {
|
||||||
Abort(c, http.StatusForbidden, i18n.ErrPublic)
|
Abort(c, http.StatusForbidden, i18n.ErrPublic)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue