UX: Add UI settings for selecting start page and time zone #577

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-03-15 14:51:44 +01:00
parent 32cbd1ade9
commit 0350e5d572
26 changed files with 254 additions and 84 deletions

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-11 19:22+0000\n"
"POT-Creation-Date: 2025-03-15 12:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -595,6 +595,51 @@ export default class Config {
return this;
}
// getDefaultRoute returns the default route to use after login or in case of routing errors.
getDefaultRoute() {
const albumsRoute = "albums";
const browseRoute = "browse";
if (this.allow("photos", "access_library")) {
const features = this.getSettings()?.features;
const startPage = this.getSettings()?.ui?.startPage;
if (features && typeof features === "object" && startPage && typeof startPage === "string") {
switch (startPage) {
case "default":
case "browse":
return browseRoute;
case "albums":
return features.albums ? startPage : browseRoute;
case "photos":
return features.albums ? startPage : browseRoute;
case "videos":
return features.library ? startPage : browseRoute;
case "people":
return features.people && features.edit ? startPage : browseRoute;
case "favorites":
return features.favorites ? startPage : browseRoute;
case "places":
return features.places ? startPage : browseRoute;
case "calendar":
return features.calendar ? startPage : browseRoute;
case "moments":
return features.moments ? startPage : browseRoute;
case "labels":
return features.labels ? startPage : browseRoute;
case "folders":
return features.folders ? startPage : browseRoute;
default:
return browseRoute;
}
}
return browseRoute;
}
return albumsRoute;
}
// getValues returns all client configuration values as exposed by the backend.
getValues() {
return this.values;

View file

@ -351,11 +351,9 @@ export default class Session {
getDefaultRoute() {
if (this.loginRequired()) {
return LoginPage;
} else if (this.config.allow("photos", "access_library")) {
return "browse";
} else {
return "albums";
}
return this.config.getDefaultRoute();
}
isAdmin() {

View file

@ -461,7 +461,7 @@
</template>
<v-list-item
v-if="isMini && $config.feature('moments')"
v-if="isMini && $config.feature('calendar')"
:to="{ name: 'calendar' }"
variant="text"
class="nav-calendar"
@ -471,7 +471,7 @@
<v-icon class="ma-auto">mdi-calendar</v-icon>
</v-list-item>
<v-list-item
v-else-if="!isMini && $config.feature('moments')"
v-else-if="!isMini && $config.feature('calendar')"
:to="{ name: 'calendar' }"
variant="text"
class="nav-calendar"

View file

@ -32,9 +32,9 @@ export const GmtOffsets = [
{ ID: "UTC-12", Name: "Etc/GMT-12:00" },
];
export const TimeZones = () =>
export const TimeZones = (defaultName) =>
[
{ ID: "", Name: $gettext("Local Time") },
{ ID: "", Name: defaultName ? defaultName : $gettext("Local Time") },
{ ID: "UTC", Name: "UTC" },
]
.concat(timeZonesNames)
@ -305,6 +305,20 @@ export const ItemsPerPage = () => [
{ text: "100", title: "100", value: 100 },
];
export const StartPages = (features) => [
{ value: "default", text: $gettext("Default"), visible: true },
{ value: "browse", text: $gettext("Search"), props: { disabled: !features?.library } },
{ value: "albums", text: $gettext("Albums"), props: { disabled: !features?.albums } },
{ value: "videos", text: $gettext("Videos"), props: { disabled: !features?.videos } },
{ value: "people", text: $gettext("People"), props: { disabled: !(features?.people && features?.edit) } },
{ value: "favorites", text: $gettext("Favorites"), props: { disabled: !features?.favorites } },
{ value: "places", text: $gettext("Places"), props: { disabled: !features?.places } },
{ value: "calendar", text: $gettext("Calendar"), props: { disabled: !features?.calendar } },
{ value: "moments", text: $gettext("Moments"), props: { disabled: !features?.moments } },
{ value: "labels", text: $gettext("Labels"), props: { disabled: !features?.labels } },
{ value: "folders", text: $gettext("Folders"), props: { disabled: !features?.folders } },
];
export const MapsAnimate = () => [
{
text: $gettext("None"),

View file

@ -70,7 +70,7 @@ export default {
label: this.$gettext("Content"),
class: "",
path: "/settings/content",
icon: "mdi-camera",
icon: "mdi-camera-iris",
public: true,
admin: true,
demo: true,

View file

@ -186,7 +186,7 @@
</v-card-actions>
</v-card>
<v-card v-if="settings.features.download" flat tile class="mt-0 px-1 bg-background">
<v-card v-if="settings.features.library && settings.features.download" flat tile class="mt-0 px-1 bg-background">
<v-card-title class="pb-0 text-subtitle-2">
{{ $gettext(`Download`) }}
</v-card-title>
@ -201,7 +201,7 @@
density="compact"
:label="$gettext('Originals')"
:hint="$gettext('Download only original media files, without any automatically generated files.')"
prepend-icon="mdi-camera-iris"
prepend-icon="mdi-camera"
persistent-hint
@update:model-value="onChange"
>

View file

@ -28,6 +28,21 @@
></v-select>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="settings.ui.startPage"
:disabled="busy"
:items="options.StartPages(settings.features)"
item-title="text"
item-value="value"
:label="$gettext('Start Page')"
:menu-props="{ maxHeight: 346 }"
hide-details
class="input-startpage"
@update:model-value="onChange"
></v-select>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="settings.ui.language"
@ -42,6 +57,20 @@
@update:model-value="onChange"
></v-select>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="settings.ui.timeZone"
:disabled="busy"
item-value="ID"
item-title="Name"
:items="options.TimeZones($gettext('Default'))"
:label="$gettext('Time Zone')"
:menu-props="{ maxHeight: 346 }"
class="input-timezone"
@update:model-value="onChangeTheme"
></v-select>
</v-col>
</v-row>
</v-card-actions>
</v-card>
@ -64,6 +93,21 @@
</v-checkbox>
</v-col>
<v-col cols="12" sm="6" lg="3" class="px-2 pb-2 pt-2">
<v-checkbox
v-model="settings.features.calendar"
:disabled="busy"
class="ma-0 pa-0 input-calendar"
density="compact"
:label="$gettext('Calendar')"
:hint="$gettext('Browse and share your pictures organized into monthly albums.')"
prepend-icon="mdi-calendar"
persistent-hint
@update:model-value="onChange"
>
</v-checkbox>
</v-col>
<v-col cols="12" sm="6" lg="3" class="px-2 pb-2 pt-2">
<v-checkbox
v-model="settings.features.moments"

View file

@ -73,24 +73,36 @@ func SaveSettings(router *gin.RouterGroup) {
// Only super admins can change global config defaults.
if s.User().IsSuperAdmin() {
// Update global defaults and user preferences.
user := s.User()
settings = conf.Settings()
// Set values from request.
if err := c.BindJSON(settings); err != nil {
AbortBadRequest(c)
return
}
// Update global defaults.
if err := settings.Save(conf.SettingsYaml()); err != nil {
log.Debugf("config: %s (save app settings)", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return
}
// Flush session cache and update client config.
// Update user preferences.
if err := user.Settings().Apply(settings).Save(); err != nil {
log.Debugf("config: %s (save user settings)", err)
AbortSaveFailed(c)
return
}
// Flush session cache and update client config
// after global settings have been updated.
entity.FlushSessionCache()
UpdateClientConfig()
} else {
// Apply to user preferences and keep current values if unspecified.
// Update user preferences without changing global defaults.
user := s.User()
if user == nil {
@ -100,11 +112,13 @@ func SaveSettings(router *gin.RouterGroup) {
settings = &customize.Settings{}
// Set values from request.
if err := c.BindJSON(settings); err != nil {
AbortBadRequest(c)
return
}
// Update user preferences.
if acl.Rules.DenyAll(acl.ResourceSettings, s.UserRole(), acl.Permissions{acl.ActionUpdate, acl.ActionManage}) {
c.JSON(http.StatusOK, user.Settings().Apply(settings).ApplyTo(conf.Settings().ApplyACL(acl.Rules, user.AclRole())))
return

View file

@ -44,8 +44,8 @@ const (
ResourceVideos Resource = "videos"
ResourceFavorites Resource = "favorites"
ResourceAlbums Resource = "albums"
ResourceMoments Resource = "moments"
ResourceCalendar Resource = "calendar"
ResourceMoments Resource = "moments"
ResourcePeople Resource = "people"
ResourcePlaces Resource = "places"
ResourceLabels Resource = "labels"

View file

@ -142,6 +142,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
Labels: true,
Library: true,
Logs: true,
Calendar: true,
Moments: true,
People: true,
Places: true,
@ -180,6 +181,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: false,
Places: true,
@ -218,6 +220,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: false,
Places: true,
@ -243,6 +246,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
assert.False(t, f.Search)
assert.False(t, f.Videos)
assert.False(t, f.Albums)
assert.False(t, f.Calendar)
assert.False(t, f.Moments)
assert.False(t, f.Labels)
assert.False(t, f.People)
@ -285,6 +289,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Search)
assert.True(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Labels)
assert.True(t, f.People)
@ -315,6 +320,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Search)
assert.True(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Labels)
assert.True(t, f.People)
@ -375,6 +381,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.False(t, f.Search)
assert.False(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Folders)
assert.False(t, f.Labels)
@ -406,6 +413,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.False(t, f.Search)
assert.False(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Folders)
assert.False(t, f.Labels)
@ -467,6 +475,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Search)
assert.True(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Labels)
assert.True(t, f.People)
@ -528,6 +537,7 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Search)
assert.True(t, f.Videos)
assert.True(t, f.Albums)
assert.True(t, f.Calendar)
assert.True(t, f.Moments)
assert.True(t, f.Labels)
assert.True(t, f.People)

View file

@ -13,6 +13,7 @@ func (s *Settings) ApplyACL(list acl.ACL, role acl.Role) *Settings {
m.Features.Favorites = s.Features.Favorites && list.AllowAny(acl.ResourceFavorites, role, acl.Permissions{acl.ActionSearch})
m.Features.Folders = s.Features.Folders && list.AllowAny(acl.ResourceFolders, role, acl.Permissions{acl.ActionSearch})
m.Features.Labels = s.Features.Labels && list.AllowAny(acl.ResourceLabels, role, acl.Permissions{acl.ActionSearch})
m.Features.Calendar = s.Features.Calendar && list.AllowAny(acl.ResourceCalendar, role, acl.Permissions{acl.ActionSearch})
m.Features.Moments = s.Features.Moments && list.AllowAny(acl.ResourceMoments, role, acl.Permissions{acl.ActionSearch})
m.Features.People = s.Features.People && list.AllowAny(acl.ResourcePeople, role, acl.Permissions{acl.ActionSearch})
m.Features.Places = s.Features.Places && list.AllowAny(acl.ResourcePlaces, role, acl.Permissions{acl.ActionSearch, acl.ActionView})

View file

@ -29,6 +29,7 @@ func TestSettings_ApplyACL(t *testing.T) {
Labels: true,
Library: true,
Logs: true,
Calendar: true,
Moments: true,
People: true,
Places: true,
@ -69,6 +70,7 @@ func TestSettings_ApplyACL(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: false,
Places: true,

View file

@ -16,6 +16,7 @@ type FeatureSettings struct {
Labels bool `json:"labels" yaml:"Labels"`
Library bool `json:"library" yaml:"Library"`
Logs bool `json:"logs" yaml:"Logs"`
Calendar bool `json:"calendar" yaml:"Calendar"`
Moments bool `json:"moments" yaml:"Moments"`
People bool `json:"people" yaml:"People"`
Places bool `json:"places" yaml:"Places"`

View file

@ -26,6 +26,7 @@ func (s *Settings) ApplyScope(scope string) *Settings {
m.Features.Favorites = s.Features.Favorites && scopes.Contains(acl.ResourceFavorites.String())
m.Features.Folders = s.Features.Folders && scopes.Contains(acl.ResourceFolders.String())
m.Features.Labels = s.Features.Labels && scopes.Contains(acl.ResourceLabels.String())
m.Features.Calendar = s.Features.Calendar && scopes.Contains(acl.ResourceCalendar.String())
m.Features.Moments = s.Features.Moments && scopes.Contains(acl.ResourceMoments.String())
m.Features.People = s.Features.People && scopes.Contains(acl.ResourcePeople.String())
m.Features.Places = s.Features.Places && scopes.Contains(acl.ResourcePlaces.String())

View file

@ -33,6 +33,7 @@ func TestSettings_ApplyScope(t *testing.T) {
Labels: true,
Library: true,
Logs: true,
Calendar: true,
Moments: true,
People: true,
Places: true,
@ -73,6 +74,7 @@ func TestSettings_ApplyScope(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: true,
Places: true,
@ -89,7 +91,7 @@ func TestSettings_ApplyScope(t *testing.T) {
}
assert.Equal(t, original, s.Features)
result := client.ApplyScope("photos videos albums places people moments")
result := client.ApplyScope("photos videos albums places people calendar moments")
t.Logf("ClientScoped: %#v", result)
assert.Equal(t, expected, result.Features)
@ -113,6 +115,7 @@ func TestSettings_ApplyScope(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: false,
Places: true,
@ -152,6 +155,7 @@ func TestSettings_ApplyScope(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: true,
Moments: true,
People: false,
Places: true,
@ -191,6 +195,7 @@ func TestSettings_ApplyScope(t *testing.T) {
Labels: false,
Library: false,
Logs: false,
Calendar: false,
Moments: false,
People: false,
Places: false,

View file

@ -42,6 +42,8 @@ func NewSettings(theme, lang string) *Settings {
Zoom: false,
Theme: theme,
Language: lang,
TimeZone: "",
StartPage: "",
},
Search: SearchSettings{
BatchSize: -1,
@ -64,6 +66,7 @@ func NewSettings(theme, lang string) *Settings {
Videos: true,
Folders: true,
Albums: true,
Calendar: true,
Moments: true,
Estimates: true,
People: true,

View file

@ -4,6 +4,7 @@ UI:
Theme: onyx
Language: de
TimeZone: ""
StartPage: ""
Search:
BatchSize: -1
ListView: true
@ -27,6 +28,7 @@ Features:
Labels: true
Library: true
Logs: true
Calendar: true
Moments: true
People: true
Places: true

View file

@ -7,4 +7,5 @@ type UISettings struct {
Theme string `json:"theme" yaml:"Theme"`
Language string `json:"language" yaml:"Language"`
TimeZone string `json:"timeZone" yaml:"TimeZone"`
StartPage string `json:"startPage" yaml:"StartPage"`
}

View file

@ -5,6 +5,7 @@ import (
"time"
"github.com/photoprism/photoprism/internal/config/customize"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
)
@ -12,6 +13,7 @@ import (
type UserSettings struct {
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"-" yaml:"UserUID"`
UITheme string `gorm:"type:VARBINARY(32);column:ui_theme;" json:"UITheme,omitempty" yaml:"UITheme,omitempty"`
UIStartPage string `gorm:"size:64;column:ui_start_page;default:'default';" json:"UIStartPage,omitempty" yaml:"UIStartPage,omitempty"`
UILanguage string `gorm:"type:VARBINARY(32);column:ui_language;" json:"UILanguage,omitempty" yaml:"UILanguage,omitempty"`
UITimeZone string `gorm:"type:VARBINARY(64);column:ui_time_zone;" json:"UITimeZone,omitempty" yaml:"UITimeZone,omitempty"`
MapsStyle string `gorm:"type:VARBINARY(32);" json:"MapsStyle,omitempty" yaml:"MapsStyle,omitempty"`
@ -27,7 +29,6 @@ type UserSettings struct {
SearchShowTitles int `gorm:"default:0;" json:"SearchShowTitles,omitempty" yaml:"SearchShowTitles,omitempty"`
SearchShowCaptions int `gorm:"default:0;" json:"SearchShowCaptions,omitempty" yaml:"SearchShowCaptions,omitempty"`
UploadPath string `gorm:"type:VARBINARY(1024);" json:"UploadPath,omitempty" yaml:"UploadPath,omitempty"`
DefaultPage string `gorm:"type:VARBINARY(128);" json:"DefaultPage,omitempty" yaml:"DefaultPage,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
}
@ -85,20 +86,24 @@ func (m *UserSettings) Updates(values interface{}) error {
func (m *UserSettings) Apply(s *customize.Settings) *UserSettings {
// UI preferences.
if s.UI.Theme != "" {
m.UITheme = s.UI.Theme
m.UITheme = clean.Type(s.UI.Theme)
}
if s.UI.StartPage != "" {
m.UIStartPage = clean.Type(s.UI.StartPage)
}
if s.UI.Language != "" {
m.UILanguage = s.UI.Language
m.UILanguage = clean.Type(s.UI.Language)
}
if s.UI.TimeZone != "" {
m.UITimeZone = s.UI.TimeZone
m.UITimeZone = clean.Type(s.UI.TimeZone)
}
// Maps preferences.
if s.Maps.Style != "" {
m.MapsStyle = s.Maps.Style
m.MapsStyle = clean.Type(s.Maps.Style)
if s.Maps.Animate > 0 {
m.MapsAnimate = s.Maps.Animate
@ -180,6 +185,12 @@ func (m *UserSettings) ApplyTo(s *customize.Settings) *customize.Settings {
s.UI.Theme = m.UITheme
}
if m.UIStartPage != "" {
s.UI.StartPage = clean.Type(m.UIStartPage)
} else if s.UI.StartPage == "" {
s.UI.StartPage = "default"
}
if m.UILanguage != "" {
s.UI.Language = m.UILanguage
}

View file

@ -207,4 +207,10 @@ var DialectMySQL = Migrations{
Stage: "pre",
Statements: []string{"ALTER TABLE photos CHANGE COLUMN IF EXISTS photo_description photo_caption VARCHAR(4096);", "ALTER TABLE photos CHANGE COLUMN IF EXISTS description_src caption_src VARBINARY(8);"},
},
{
ID: "20250315-000001",
Dialect: "mysql",
Stage: "pre",
Statements: []string{"ALTER TABLE auth_users_settings CHANGE COLUMN IF EXISTS default_page ui_start_page VARCHAR(64) DEFAULT 'default';"},
},
}

View file

@ -123,4 +123,10 @@ var DialectSQLite3 = Migrations{
Stage: "pre",
Statements: []string{"ALTER TABLE photos RENAME COLUMN photo_description TO photo_caption;", "ALTER TABLE photos RENAME COLUMN description_src TO caption_src;"},
},
{
ID: "20250315-000001",
Dialect: "sqlite3",
Stage: "pre",
Statements: []string{"ALTER TABLE auth_users_settings RENAME COLUMN default_page TO ui_start_page;"},
},
}

View file

@ -0,0 +1 @@
ALTER TABLE auth_users_settings CHANGE COLUMN IF EXISTS default_page ui_start_page VARCHAR(64) DEFAULT 'default';

View file

@ -0,0 +1 @@
ALTER TABLE auth_users_settings RENAME COLUMN default_page TO ui_start_page;

View file

@ -160,6 +160,7 @@ classDiagram
}
class auth_users_settings {
varbinary(32) ui_theme
varchar(64) ui_start_page
varbinary(32) ui_language
varbinary(64) ui_time_zone
varbinary(32) maps_style
@ -172,7 +173,6 @@ classDiagram
int(11) download_media_raw
int(11) download_media_sidecar
varbinary(1024) upload_path
varbinary(128) default_page
datetime created_at
datetime updated_at
varbinary(42) user_uid

View file

@ -1,10 +1,10 @@
/*!999999\- enable the sandbox mode */
/*M!999999\- enable the sandbox mode */
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `albums` (
@ -13,7 +13,7 @@ CREATE TABLE `albums` (
`parent_uid` varbinary(42) DEFAULT '',
`album_slug` varbinary(160) DEFAULT NULL,
`album_path` varchar(1024) DEFAULT NULL,
`album_type` varbinary(8) DEFAULT 'album',
`album_type` varbinary(8) DEFAULT x'616c62756d',
`album_title` varchar(160) DEFAULT NULL,
`album_location` varchar(160) DEFAULT NULL,
`album_category` varchar(100) DEFAULT NULL,
@ -24,7 +24,7 @@ CREATE TABLE `albums` (
`album_order` varbinary(32) DEFAULT NULL,
`album_template` varbinary(255) DEFAULT NULL,
`album_state` varchar(100) DEFAULT NULL,
`album_country` varbinary(2) DEFAULT 'zz',
`album_country` varbinary(2) DEFAULT x'7a7a',
`album_year` int(11) DEFAULT NULL,
`album_month` int(11) DEFAULT NULL,
`album_day` int(11) DEFAULT NULL,
@ -39,17 +39,17 @@ CREATE TABLE `albums` (
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uix_albums_album_uid` (`album_uid`),
KEY `idx_albums_country_year_month` (`album_country`,`album_year`,`album_month`),
KEY `idx_albums_ymd` (`album_day`),
KEY `idx_albums_thumb` (`thumb`),
KEY `idx_albums_published_at` (`published_at`),
KEY `idx_albums_album_slug` (`album_slug`),
KEY `idx_albums_album_path` (`album_path`(768)),
KEY `idx_albums_album_title` (`album_title`),
KEY `idx_albums_country_year_month` (`album_country`,`album_year`,`album_month`),
KEY `idx_albums_created_by` (`created_by`),
KEY `idx_albums_deleted_at` (`deleted_at`),
KEY `idx_albums_album_category` (`album_category`),
KEY `idx_albums_album_state` (`album_state`),
KEY `idx_albums_deleted_at` (`deleted_at`),
KEY `idx_albums_album_title` (`album_title`),
KEY `idx_albums_thumb` (`thumb`),
KEY `idx_albums_created_by` (`created_by`),
KEY `idx_albums_published_at` (`published_at`),
KEY `idx_albums_album_filter` (`album_filter`(512))
);
/*!40101 SET character_set_client = @saved_cs_client */;
@ -81,10 +81,10 @@ CREATE TABLE `audit_logins` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`client_ip`,`login_name`,`login_realm`),
KEY `idx_audit_logins_failed_at` (`failed_at`),
KEY `idx_audit_logins_banned_at` (`banned_at`),
KEY `idx_audit_logins_updated_at` (`updated_at`),
KEY `idx_audit_logins_login_name` (`login_name`)
KEY `idx_audit_logins_login_name` (`login_name`),
KEY `idx_audit_logins_failed_at` (`failed_at`),
KEY `idx_audit_logins_banned_at` (`banned_at`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -108,8 +108,8 @@ CREATE TABLE `auth_clients` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`client_uid`),
KEY `idx_auth_clients_user_name` (`user_name`),
KEY `idx_auth_clients_user_uid` (`user_uid`)
KEY `idx_auth_clients_user_uid` (`user_uid`),
KEY `idx_auth_clients_user_name` (`user_name`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -143,12 +143,12 @@ CREATE TABLE `auth_sessions` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_auth_sessions_auth_id` (`auth_id`),
KEY `idx_auth_sessions_sess_expires` (`sess_expires`),
KEY `idx_auth_sessions_user_uid` (`user_uid`),
KEY `idx_auth_sessions_user_name` (`user_name`),
KEY `idx_auth_sessions_client_uid` (`client_uid`),
KEY `idx_auth_sessions_client_ip` (`client_ip`),
KEY `idx_auth_sessions_auth_id` (`auth_id`)
KEY `idx_auth_sessions_client_ip` (`client_ip`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -192,15 +192,15 @@ CREATE TABLE `auth_users` (
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uix_auth_users_user_uid` (`user_uid`),
KEY `idx_auth_users_thumb` (`thumb`),
KEY `idx_auth_users_deleted_at` (`deleted_at`),
KEY `idx_auth_users_auth_id` (`auth_id`),
KEY `idx_auth_users_user_name` (`user_name`),
KEY `idx_auth_users_born_at` (`born_at`),
KEY `idx_auth_users_deleted_at` (`deleted_at`),
KEY `idx_auth_users_user_uuid` (`user_uuid`),
KEY `idx_auth_users_user_email` (`user_email`),
KEY `idx_auth_users_expires_at` (`expires_at`),
KEY `idx_auth_users_invite_token` (`invite_token`),
KEY `idx_auth_users_born_at` (`born_at`),
KEY `idx_auth_users_user_uuid` (`user_uuid`),
KEY `idx_auth_users_auth_id` (`auth_id`)
KEY `idx_auth_users_thumb` (`thumb`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -209,12 +209,12 @@ CREATE TABLE `auth_users_details` (
`user_uid` varbinary(42) NOT NULL,
`subj_uid` varbinary(42) DEFAULT NULL,
`subj_src` varbinary(8) DEFAULT '',
`place_id` varbinary(42) DEFAULT 'zz',
`place_id` varbinary(42) DEFAULT x'7a7a',
`place_src` varbinary(8) DEFAULT NULL,
`cell_id` varbinary(42) DEFAULT 'zz',
`birth_year` int(11) DEFAULT NULL,
`birth_month` int(11) DEFAULT NULL,
`birth_day` int(11) DEFAULT NULL,
`cell_id` varbinary(42) DEFAULT x'7a7a',
`birth_year` int(11) DEFAULT -1,
`birth_month` int(11) DEFAULT -1,
`birth_day` int(11) DEFAULT -1,
`name_title` varchar(32) DEFAULT NULL,
`given_name` varchar(64) DEFAULT NULL,
`middle_name` varchar(64) DEFAULT NULL,
@ -226,7 +226,7 @@ CREATE TABLE `auth_users_details` (
`user_about` varchar(512) DEFAULT NULL,
`user_bio` varchar(2048) DEFAULT NULL,
`user_location` varchar(512) DEFAULT NULL,
`user_country` varbinary(2) DEFAULT NULL,
`user_country` varbinary(2) DEFAULT x'7a7a',
`user_phone` varchar(32) DEFAULT NULL,
`site_url` varbinary(512) DEFAULT NULL,
`profile_url` varbinary(512) DEFAULT NULL,
@ -241,10 +241,10 @@ CREATE TABLE `auth_users_details` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`user_uid`),
KEY `idx_auth_users_details_subj_uid` (`subj_uid`),
KEY `idx_auth_users_details_place_id` (`place_id`),
KEY `idx_auth_users_details_cell_id` (`cell_id`),
KEY `idx_auth_users_details_org_email` (`org_email`)
KEY `idx_auth_users_details_org_email` (`org_email`),
KEY `idx_auth_users_details_subj_uid` (`subj_uid`),
KEY `idx_auth_users_details_place_id` (`place_id`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -252,6 +252,7 @@ CREATE TABLE `auth_users_details` (
CREATE TABLE `auth_users_settings` (
`user_uid` varbinary(42) NOT NULL,
`ui_theme` varbinary(32) DEFAULT NULL,
`ui_start_page` varchar(64) DEFAULT 'default',
`ui_language` varbinary(32) DEFAULT NULL,
`ui_time_zone` varbinary(64) DEFAULT NULL,
`maps_style` varbinary(32) DEFAULT NULL,
@ -263,8 +264,10 @@ CREATE TABLE `auth_users_settings` (
`download_originals` int(11) DEFAULT 0,
`download_media_raw` int(11) DEFAULT 0,
`download_media_sidecar` int(11) DEFAULT 0,
`search_list_view` int(11) DEFAULT 0,
`search_show_titles` int(11) DEFAULT 0,
`search_show_captions` int(11) DEFAULT 0,
`upload_path` varbinary(1024) DEFAULT NULL,
`default_page` varbinary(128) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`user_uid`)
@ -322,7 +325,7 @@ CREATE TABLE `cells` (
`cell_street` varchar(100) DEFAULT NULL,
`cell_postcode` varchar(50) DEFAULT NULL,
`cell_category` varchar(50) DEFAULT NULL,
`place_id` varbinary(42) DEFAULT 'zz',
`place_id` varbinary(42) DEFAULT x'7a7a',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
@ -368,7 +371,7 @@ CREATE TABLE `details` (
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `duplicates` (
`file_name` varbinary(755) NOT NULL,
`file_root` varbinary(16) NOT NULL DEFAULT '/',
`file_root` varbinary(16) NOT NULL DEFAULT x'2f',
`file_hash` varbinary(128) DEFAULT '',
`file_size` bigint(20) DEFAULT NULL,
`mod_time` bigint(20) DEFAULT NULL,
@ -420,7 +423,7 @@ CREATE TABLE `files` (
`instance_id` varbinary(64) DEFAULT NULL,
`file_uid` varbinary(42) DEFAULT NULL,
`file_name` varbinary(1024) DEFAULT NULL,
`file_root` varbinary(16) DEFAULT '/',
`file_root` varbinary(16) DEFAULT x'2f',
`original_name` varbinary(755) DEFAULT NULL,
`file_hash` varbinary(128) DEFAULT NULL,
`file_size` bigint(20) DEFAULT NULL,
@ -436,6 +439,7 @@ CREATE TABLE `files` (
`file_duration` bigint(20) DEFAULT NULL,
`file_fps` double DEFAULT NULL,
`file_frames` int(11) DEFAULT NULL,
`file_pages` int(11) DEFAULT 0,
`file_width` int(11) DEFAULT NULL,
`file_height` int(11) DEFAULT NULL,
`file_orientation` int(11) DEFAULT NULL,
@ -465,14 +469,14 @@ CREATE TABLE `files` (
UNIQUE KEY `idx_files_search_media` (`media_id`),
UNIQUE KEY `idx_files_search_timeline` (`time_index`),
KEY `idx_files_photo_uid` (`photo_uid`),
KEY `idx_files_instance_id` (`instance_id`),
KEY `idx_files_file_hash` (`file_hash`),
KEY `idx_files_published_at` (`published_at`),
KEY `idx_files_file_error` (`file_error`),
KEY `idx_files_deleted_at` (`deleted_at`),
KEY `idx_files_photo_id` (`photo_id`,`file_primary`),
KEY `idx_files_photo_taken_at` (`photo_taken_at`),
KEY `idx_files_media_utc` (`media_utc`),
KEY `idx_files_instance_id` (`instance_id`),
KEY `idx_files_file_error` (`file_error`),
KEY `idx_files_deleted_at` (`deleted_at`),
KEY `idx_files_missing_root` (`file_missing`,`file_root`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
@ -518,7 +522,7 @@ CREATE TABLE `folders` (
`folder_category` varchar(100) DEFAULT NULL,
`folder_description` varchar(2048) DEFAULT NULL,
`folder_order` varbinary(32) DEFAULT NULL,
`folder_country` varbinary(2) DEFAULT 'zz',
`folder_country` varbinary(2) DEFAULT x'7a7a',
`folder_year` int(11) DEFAULT NULL,
`folder_month` int(11) DEFAULT NULL,
`folder_day` int(11) DEFAULT NULL,
@ -647,11 +651,11 @@ CREATE TABLE `markers` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`marker_uid`),
KEY `idx_markers_thumb` (`thumb`),
KEY `idx_markers_matched_at` (`matched_at`),
KEY `idx_markers_file_uid` (`file_uid`),
KEY `idx_markers_subj_uid_src` (`subj_uid`,`subj_src`),
KEY `idx_markers_face_id` (`face_id`),
KEY `idx_markers_thumb` (`thumb`),
KEY `idx_markers_matched_at` (`matched_at`)
KEY `idx_markers_face_id` (`face_id`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -700,7 +704,7 @@ CREATE TABLE `photos` (
`taken_at_local` datetime DEFAULT NULL,
`taken_src` varbinary(8) DEFAULT NULL,
`photo_uid` varbinary(42) DEFAULT NULL,
`photo_type` varbinary(8) DEFAULT 'image',
`photo_type` varbinary(8) DEFAULT x'696d616765',
`type_src` varbinary(8) DEFAULT NULL,
`photo_title` varchar(200) DEFAULT NULL,
`title_src` varbinary(8) DEFAULT NULL,
@ -715,14 +719,14 @@ CREATE TABLE `photos` (
`photo_scan` tinyint(1) DEFAULT NULL,
`photo_panorama` tinyint(1) DEFAULT NULL,
`time_zone` varbinary(64) DEFAULT NULL,
`place_id` varbinary(42) DEFAULT 'zz',
`place_id` varbinary(42) DEFAULT x'7a7a',
`place_src` varbinary(8) DEFAULT NULL,
`cell_id` varbinary(42) DEFAULT 'zz',
`cell_id` varbinary(42) DEFAULT x'7a7a',
`cell_accuracy` int(11) DEFAULT NULL,
`photo_altitude` int(11) DEFAULT NULL,
`photo_lat` float DEFAULT NULL,
`photo_lng` float DEFAULT NULL,
`photo_country` varbinary(2) DEFAULT 'zz',
`photo_lat` double DEFAULT NULL,
`photo_lng` double DEFAULT NULL,
`photo_country` varbinary(2) DEFAULT x'7a7a',
`photo_year` int(11) DEFAULT NULL,
`photo_month` int(11) DEFAULT NULL,
`photo_day` int(11) DEFAULT NULL,
@ -749,20 +753,20 @@ CREATE TABLE `photos` (
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uix_photos_photo_uid` (`photo_uid`),
KEY `idx_photos_photo_lng` (`photo_lng`),
KEY `idx_photos_camera_lens` (`camera_id`,`lens_id`),
KEY `idx_photos_taken_uid` (`taken_at`,`photo_uid`),
KEY `idx_photos_ymd` (`photo_day`),
KEY `idx_photos_checked_at` (`checked_at`),
KEY `idx_photos_uuid` (`uuid`),
KEY `idx_photos_path_name` (`photo_path`,`photo_name`),
KEY `idx_photos_place_id` (`place_id`),
KEY `idx_photos_cell_id` (`cell_id`),
KEY `idx_photos_created_by` (`created_by`),
KEY `idx_photos_deleted_at` (`deleted_at`),
KEY `idx_photos_photo_lat` (`photo_lat`),
KEY `idx_photos_country_year_month` (`photo_country`,`photo_year`,`photo_month`),
KEY `idx_photos_published_at` (`published_at`)
KEY `idx_photos_created_by` (`created_by`),
KEY `idx_photos_published_at` (`published_at`),
KEY `idx_photos_checked_at` (`checked_at`),
KEY `idx_photos_taken_uid` (`taken_at`,`photo_uid`),
KEY `idx_photos_path_name` (`photo_path`,`photo_name`),
KEY `idx_photos_cell_id` (`cell_id`),
KEY `idx_photos_photo_lng` (`photo_lng`),
KEY `idx_photos_ymd` (`photo_day`),
KEY `idx_photos_camera_lens` (`camera_id`,`lens_id`),
KEY `idx_photos_deleted_at` (`deleted_at`),
KEY `idx_photos_uuid` (`uuid`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -826,9 +830,9 @@ CREATE TABLE `places` (
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_places_place_state` (`place_state`),
KEY `idx_places_place_district` (`place_district`),
KEY `idx_places_place_city` (`place_city`)
KEY `idx_places_place_city` (`place_city`),
KEY `idx_places_place_state` (`place_state`)
);
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@ -927,5 +931,5 @@ CREATE TABLE `versions` (
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */;