Backend: Add credentials and extend person entity #98 #144

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-09-06 14:18:40 +02:00
parent 53c48cd570
commit 33888fd231
15 changed files with 469 additions and 125 deletions

View file

@ -138,9 +138,9 @@ export default class Session {
return "";
}
getFirstName() {
getGivenName() {
if (this.isUser()) {
return this.user.FirstName;
return this.user.GivenName;
}
return "";
@ -148,7 +148,7 @@ export default class Session {
getFullName() {
if (this.isUser()) {
return this.user.FirstName + " " + this.user.LastName;
return this.user.GivenName + " " + this.user.FamilyName;
}
return "";
@ -159,7 +159,7 @@ export default class Session {
}
isAdmin() {
return this.user && this.user.hasId() && this.user.Admin;
return this.user && this.user.hasId() && this.user.RoleAdmin;
}
isAnonymous() {

View file

@ -38,28 +38,81 @@ export class User extends RestModel {
return {
ID: 0,
UID: "",
ParentUID: "",
UserUUID: "",
UserName: "",
FirstName: "",
LastName: "",
UserLocale: "",
TimeZone: "",
PrimaryEmail: "",
BackupEmail: "",
DisplayName: "",
Email: "",
Info: "",
Notes: "",
Active: false,
Confirmed: false,
Admin: false,
Guest: false,
Child: false,
Family: false,
Friend: false,
Artist: false,
Subject: false,
DisplayLocation: "",
DisplayBio: "",
NamePrefix: "",
GivenName: "",
FamilyName: "",
NameSuffix: "",
AvatarUID: "",
AvatarURL: "",
FeedURL: "",
FeedType: "",
FeedFollow: false,
BlogURL: "",
BlogType: "",
BlogFollow: false,
CompanyURL: "",
CompanyName: "",
CompanyPhone: "",
PrimaryPhone: "",
DepartmentName: "",
JobTitle: "",
AddressLat: 0.0,
AddressLng: 0.0,
AddressLine1: "",
AddressLine2: "",
AddressZip: "",
AddressCity: "",
AddressState: "",
AddressCountry: "",
TermsAccepted: false,
IsActive: false,
IsConfirmed: false,
IsPro: false,
IsSponsor: false,
IsContributor: false,
IsArtist: false,
IsSubject: false,
RoleAdmin: false,
RoleGuest: false,
RoleChild: false,
RoleFamily: false,
RoleFriend: false,
CanEdit: false,
CanDelete: false,
CanIndex: false,
CanShare: false,
CanComment: false,
CanUpload: false,
CanDownload: false,
WebDAV: false,
ApiToken: "",
AmazonID: "",
AppleID: "",
EyeEmID: "",
FacebookID: "",
FlickrID: "",
GitHubID: "",
GitLabID: "",
GoogleID: "",
InstagramID: "",
LinkedinID: "",
MastodonID: "",
NextcloudID: "",
TelegramID: "",
TwitterID: "",
WhatsAppID: "",
YouTubeID: "",
UserNotes: "",
LoginAttempts: 0,
LoginAt: "",
CreatedAt: "",
@ -68,7 +121,7 @@ export class User extends RestModel {
}
getEntityName() {
return this.FirstName + " " + this.LastName;
return this.GivenName + " " + this.FamilyName;
}
getRegisterForm() {

View file

@ -185,12 +185,12 @@ describe('common/session', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
assert.isFalse(session.user.hasId());
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData();
assert.equal(session.user.FirstName, "");
assert.equal(session.user.GivenName, "");
session.setData(values);
assert.equal(session.user.FirstName, "Max");
assert.equal(session.user.Admin, true);
assert.equal(session.user.GivenName, "Max");
assert.equal(session.user.RoleAdmin, true);
const result = session.getUser();
assert.equal(result.ID, 5);
assert.equal(result.Email, "test@test.com");
@ -201,11 +201,11 @@ describe('common/session', () => {
it('should get user email', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getEmail();
assert.equal(result, "test@test.com");
const values2 = {"user": {FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getEmail();
assert.equal(result2, "");
@ -215,13 +215,13 @@ describe('common/session', () => {
it('should get user firstname', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getFirstName();
const result = session.getGivenName();
assert.equal(result, "Max");
const values2 = {"user": {FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getFirstName();
const result2 = session.getGivenName();
assert.equal(result2, "");
session.deleteData();
});
@ -229,11 +229,11 @@ describe('common/session', () => {
it('should get user full name', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.getFullName();
assert.equal(result, "Max Last");
const values2 = {"user": {FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values2 = {"user": {GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values2);
const result2 = session.getFullName();
assert.equal(result2, "");
@ -243,7 +243,7 @@ describe('common/session', () => {
it('should test whether user is set', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isUser();
assert.equal(result, true);
@ -253,7 +253,7 @@ describe('common/session', () => {
it('should test whether user is admin', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isAdmin();
assert.equal(result, true);
@ -263,7 +263,7 @@ describe('common/session', () => {
it('should test whether user is anonymous', () => {
const storage = window.localStorage;
const session = new Session(storage, config);
const values = {"user": {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Admin: true}};
const values = {"user": {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", RoleAdmin: true}};
session.setData(values);
const result = session.isAnonymous();
assert.equal(result, false);

View file

@ -9,14 +9,14 @@ describe("model/user", () => {
const mock = new MockAdapter(Api);
it("should get entity name", () => {
const values = {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
const values = {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", Role: "admin"};
const user = new User(values);
const result = user.getEntityName();
assert.equal(result, "Max Last");
});
it("should get id", () => {
const values = {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
const values = {ID: 5, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", Role: "admin"};
const user = new User(values);
const result = user.getId();
assert.equal(result, 5);
@ -34,7 +34,7 @@ describe("model/user", () => {
it("should get register form", async() => {
mock.onAny("users/52/register").reply(200, "registerForm");
const values = {ID: 52, FirstName: "Max"};
const values = {ID: 52, GivenName: "Max"};
const user = new User(values);
const result = await user.getRegisterForm();
assert.equal(result.definition, "registerForm");
@ -43,7 +43,7 @@ describe("model/user", () => {
it("should get profile form", async() => {
mock.onAny("users/53/profile").reply(200, "profileForm");
const values = {ID: 53, FirstName: "Max"};
const values = {ID: 53, GivenName: "Max"};
const user = new User(values);
const result = await user.getProfileForm();
assert.equal(result.definition, "profileForm");
@ -52,20 +52,20 @@ describe("model/user", () => {
it("should get change password", async() => {
mock.onPut("users/54/password").reply(200, {password: "old", new_password: "new"});
const values = {ID: 54, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
const values = {ID: 54, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", Role: "admin"};
const user = new User(values);
const result = await user.changePassword("old", "new");
assert.equal(result.new_password, "new");
});
it("should save profile", async() => {
mock.onPost("users/55/profile").reply(200, {FirstName: "MaxNew", LastName: "LastNew"});
const values = {ID: 55, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
mock.onPost("users/55/profile").reply(200, {GivenName: "MaxNew", FamilyName: "LastNew"});
const values = {ID: 55, GivenName: "Max", FamilyName: "Last", Email: "test@test.com", Role: "admin"};
const user = new User(values);
assert.equal(user.FirstName, "Max");
assert.equal(user.LastName, "Last");
assert.equal(user.GivenName, "Max");
assert.equal(user.FamilyName, "Last");
await user.saveProfile();
assert.equal(user.FirstName, "MaxNew");
assert.equal(user.LastName, "LastNew");
assert.equal(user.GivenName, "MaxNew");
assert.equal(user.FamilyName, "LastNew");
});
});

View file

@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/pkg/txt"
)
// ClientConfig contains HTTP client / Web UI config values
// ClientConfig represents HTTP client / Web UI config values.
type ClientConfig struct {
Name string `json:"name"`
Version string `json:"version"`
@ -26,6 +26,9 @@ type ClientConfig struct {
ReadOnly bool `json:"readonly"`
UploadNSFW bool `json:"uploadNSFW"`
Public bool `json:"public"`
Pro bool `json:"pro"`
Sponsor bool `json:"sponsor"`
Contributor bool `json:"contributor"`
Experimental bool `json:"experimental"`
DisableSettings bool `json:"disableSettings"`
AlbumCategories []string `json:"albumCategories"`
@ -34,6 +37,8 @@ type ClientConfig struct {
Lenses []entity.Lens `json:"lenses"`
Countries []entity.Country `json:"countries"`
Thumbs []Thumb `json:"thumbs"`
ApiKey string `json:"apiKey"`
MapsKey string `json:"mapsKey"`
DownloadToken string `json:"downloadToken"`
PreviewToken string `json:"previewToken"`
JSHash string `json:"jsHash"`

View file

@ -2,6 +2,7 @@ package config
import (
"context"
"fmt"
"runtime"
"strings"
"sync"
@ -11,6 +12,7 @@ import (
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/rnd"
@ -23,11 +25,12 @@ var once sync.Once
// Config holds database, cache and all parameters of photoprism
type Config struct {
once sync.Once
db *gorm.DB
params *Params
settings *Settings
token string
once sync.Once
db *gorm.DB
params *Params
settings *Settings
credentials *Credentials
token string
}
func init() {
@ -67,6 +70,7 @@ func NewConfig(ctx *cli.Context) *Config {
}
c.initSettings()
c.initCredentials()
return c
}
@ -79,12 +83,14 @@ func (c *Config) Propagate() {
thumb.SizeUncached = c.ThumbSizeUncached()
thumb.Filter = c.ThumbFilter()
thumb.JpegQuality = c.JpegQuality()
places.UserAgent = c.UserAgent()
c.Settings().Propagate()
c.Credentials().Propagate()
}
// Init initialises the database connection and dependencies.
func (c *Config) Init(ctx context.Context) error {
func (c *Config) Init(_ context.Context) error {
c.Propagate()
return c.connectDb()
}
@ -99,6 +105,11 @@ func (c *Config) Version() string {
return c.params.Version
}
// UserAgent returns a HTTP user agent string based on app name & version.
func (c *Config) UserAgent() string {
return fmt.Sprintf("%s/%s", c.Name(), c.Version())
}
// Copyright returns the application copyright.
func (c *Config) Copyright() string {
return c.params.Copyright

View file

@ -0,0 +1,107 @@
package config
import (
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
"gopkg.in/yaml.v2"
)
// Credentials represents api credentials for hosted services like maps & places.
type Credentials struct {
Key string `json:"key" yaml:"key"`
Secret string `json:"secret" yaml:"secret"`
Session string `json:"session" yaml:"session"`
}
// NewCredentials creates a new Credentials instance.
func NewCredentials() *Credentials {
return &Credentials{
Key: "",
Secret: "",
Session: "",
}
}
// Propagate updates api credentials in other packages.
func (a *Credentials) Propagate() {
places.Key = a.Key
}
// Sanitize verifies and sanitizes api credentials;
func (a *Credentials) Sanitize() {
a.Key = strings.ToLower(a.Key)
if a.Secret != "" {
if a.Key != fmt.Sprintf("%x", sha1.Sum([]byte(a.Secret))) {
a.Secret = ""
a.Session = ""
}
}
}
// Load api credentials from a file.
func (a *Credentials) Load(fileName string) error {
if !fs.FileExists(fileName) {
return fmt.Errorf("credentials file not found: %s", txt.Quote(fileName))
}
yamlConfig, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
if err := yaml.Unmarshal(yamlConfig, a); err != nil {
return err
}
a.Sanitize()
a.Propagate()
return nil
}
// Save api credentials to a file.
func (a *Credentials) Save(fileName string) error {
a.Sanitize()
data, err := yaml.Marshal(a)
if err != nil {
return err
}
a.Propagate()
if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil {
return err
}
a.Propagate()
return nil
}
// initCredentials initializes the api credentials.
func (c *Config) initCredentials() {
c.credentials = NewCredentials()
p := c.CredentialsFile()
if err := c.credentials.Load(p); err != nil {
log.Traceln(err)
}
c.credentials.Propagate()
}
// Credentials returns the api key instance.
func (c *Config) Credentials() *Credentials {
return c.credentials
}

View file

@ -0,0 +1,84 @@
package config
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCredentials(t *testing.T) {
c := NewCredentials()
assert.IsType(t, &Credentials{}, c)
}
func TestCredentials_Load(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
c := NewCredentials()
if err := c.Load("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "photoprism", c.Secret)
assert.Equal(t, "Zm9vYmFy", c.Session)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewCredentials()
if err := c.Load("testdata/credentials_xxx.yml"); err == nil {
t.Fatal("file should not exist")
}
assert.Equal(t, "", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
})
}
func TestCredentials_Save(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
assert.FileExists(t, "testdata/credentials.yml")
c := NewCredentials()
if err := c.Load("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "photoprism", c.Secret)
assert.Equal(t, "Zm9vYmFy", c.Session)
if err := c.Save("testdata/credentials.yml"); err != nil {
t.Fatal(err)
}
assert.FileExists(t, "testdata/credentials.yml")
})
t.Run("not existing filename", func(t *testing.T) {
c := NewCredentials()
c.Key = "F60F5B25D59C397989E3CD374F81CDD7710A4FCA"
c.Secret = "foo"
c.Session = "bar"
assert.Equal(t, "F60F5B25D59C397989E3CD374F81CDD7710A4FCA", c.Key)
assert.Equal(t, "foo", c.Secret)
assert.Equal(t, "bar", c.Session)
if err := c.Save("testdata/credentials_new.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "f60f5b25d59c397989e3cd374f81cdd7710a4fca", c.Key)
assert.Equal(t, "", c.Secret)
assert.Equal(t, "", c.Session)
assert.FileExists(t, "testdata/credentials_new.yml")
if err := os.Remove("testdata/credentials_new.yml"); err != nil {
t.Fatal(err)
}
})
}

View file

@ -137,6 +137,11 @@ func (c *Config) ConfigFile() string {
return c.params.ConfigFile
}
// CredentialsFile returns the api credentials file name for hosted services like maps & places.
func (c *Config) CredentialsFile() string {
return filepath.Join(c.SettingsPath(), "credentials.yml")
}
// SettingsFile returns the user settings file name.
func (c *Config) SettingsFile() string {
return filepath.Join(c.SettingsPath(), "settings.yml")
@ -194,7 +199,7 @@ func (c *Config) ExifToolBin() string {
return findExecutable(c.params.ExifToolBin, "exiftool")
}
// SidecarJson returns true if metadata should be synced with json sidecar files as used by exiftool.
// Automatically create JSON sidecar files using Exiftool.
func (c *Config) SidecarJson() bool {
if !c.SidecarWritable() || c.ExifToolBin() == "" {
return false
@ -203,7 +208,7 @@ func (c *Config) SidecarJson() bool {
return c.params.SidecarJson
}
// SidecarYaml returns true if metadata should be synced with PhotoPrism YAML sidecar files.
// Automatically backup metadata to YAML sidecar files.
func (c *Config) SidecarYaml() bool {
if !c.SidecarWritable() {
return false
@ -212,7 +217,7 @@ func (c *Config) SidecarYaml() bool {
return c.params.SidecarYaml
}
// SidecarPath returns the storage path for automatically created sidecar files.
// SidecarPath returns the storage path for generated sidecar files (relative or absolute).
func (c *Config) SidecarPath() string {
if c.params.SidecarPath == "" {
c.params.SidecarPath = filepath.Join(c.StoragePath(), "sidecar")

View file

@ -16,15 +16,18 @@ func (c *Config) SettingsHidden() bool {
return c.params.SettingsHidden
}
// TemplateSettings represents HTML template settings for the Web UI.
type TemplateSettings struct {
Default string `json:"default" yaml:"default"`
}
// MapsSettings represents maps settings (for places).
type MapsSettings struct {
Animate int `json:"animate" yaml:"animate"`
Style string `json:"style" yaml:"style"`
}
// IndexSettings represents indexing settings.
type IndexSettings struct {
Path string `json:"path" yaml:"path"`
Convert bool `json:"convert" yaml:"convert"`
@ -32,11 +35,13 @@ type IndexSettings struct {
Sequences bool `json:"sequences" yaml:"sequences"`
}
// ImportSettings represents import settings.
type ImportSettings struct {
Path string `json:"path" yaml:"path"`
Move bool `json:"move" yaml:"move"`
}
// FeatureSettings represents feature flags, mainly for the Web UI.
type FeatureSettings struct {
Upload bool `json:"upload" yaml:"upload"`
Download bool `json:"download" yaml:"download"`
@ -54,7 +59,7 @@ type FeatureSettings struct {
Logs bool `json:"logs" yaml:"logs"`
}
// Settings contains Web UI settings
// Settings represents user settings for Web UI, indexing, and import.
type Settings struct {
Theme string `json:"theme" yaml:"theme"`
Language string `json:"language" yaml:"language"`
@ -65,7 +70,7 @@ type Settings struct {
Index IndexSettings `json:"index" yaml:"index"`
}
// NewSettings returns a empty Settings
// NewSettings creates a new Settings instance.
func NewSettings() *Settings {
return &Settings{
Theme: "default",
@ -111,7 +116,7 @@ func (s Settings) Propagate() {
i18n.SetLocale(s.Language)
}
// Load uses a yaml config file to initiate the configuration entity.
// Load user settings from file.
func (s *Settings) Load(fileName string) error {
if !fs.FileExists(fileName) {
return fmt.Errorf("settings file not found: %s", txt.Quote(fileName))
@ -132,7 +137,7 @@ func (s *Settings) Load(fileName string) error {
return nil
}
// Save uses a yaml config file to initiate the configuration entity.
// Save user settings to a file.
func (s *Settings) Save(fileName string) error {
data, err := yaml.Marshal(s)

View file

@ -9,6 +9,7 @@ import (
func TestNewSettings(t *testing.T) {
c := NewSettings()
assert.IsType(t, new(Settings), c)
}
@ -16,9 +17,9 @@ func TestSettings_Load(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
c := NewSettings()
err := c.Load("testdata/config.yml")
assert.Nil(t, err)
if err := c.Load("testdata/config.yml"); err != nil {
t.Fatal(err)
}
assert.Equal(t, "lavendel", c.Theme)
assert.Equal(t, "english", c.Language)
@ -43,9 +44,9 @@ func TestSettings_Save(t *testing.T) {
assert.Equal(t, "lavendel", c.Theme)
assert.Equal(t, "german", c.Language)
err := c.Save("testdata/configEmpty.yml")
assert.Nil(t, err)
if err := c.Save("testdata/configEmpty.yml"); err != nil {
t.Fatal(err)
}
})
t.Run("not existing filename", func(t *testing.T) {
c := NewSettings()
@ -55,9 +56,7 @@ func TestSettings_Save(t *testing.T) {
assert.Equal(t, "lavendel", c.Theme)
assert.Equal(t, "german", c.Language)
err := c.Save("testdata/configEmpty123.yml")
if err != nil {
if err := c.Save("testdata/configEmpty123.yml"); err != nil {
t.Fatal(err)
}

View file

@ -130,6 +130,7 @@ func NewTestConfig() *Config {
}
c.initSettings()
c.initCredentials()
if err := c.Init(context.Background()); err != nil {
log.Fatalf("config: %s", err.Error())
@ -150,6 +151,7 @@ func NewTestErrorConfig() *Config {
c := &Config{params: NewTestParamsError()}
c.initSettings()
c.initCredentials()
if err := c.Init(context.Background()); err != nil {
log.Fatalf("config: %s", err.Error())

View file

@ -0,0 +1,3 @@
key: f60f5b25d59c397989e3cd374f81cdd7710a4fca
secret: photoprism
session: Zm9vYmFy

View file

@ -14,72 +14,138 @@ type People []Person
// Person represents a real person that can also be a user if a password is set.
type Person struct {
ID int `gorm:"primary_key" json:"ID" yaml:"-"`
PersonUID string `gorm:"type:varbinary(42);unique_index;" json:"UID" yaml:"UID"`
UserName string `gorm:"type:varchar(32);" json:"UserName" yaml:"UserName,omitempty"`
FirstName string `gorm:"type:varchar(32);" json:"FirstName" yaml:"FirstName,omitempty"`
LastName string `gorm:"type:varchar(32);" json:"LastName" yaml:"LastName,omitempty"`
DisplayName string `gorm:"type:varchar(64);" json:"DisplayName" yaml:"DisplayName,omitempty"`
UserEmail string `gorm:"type:varchar(255);" json:"Email" yaml:"Email,omitempty"`
UserInfo string `gorm:"type:text;" json:"Info" yaml:"Info,omitempty"`
UserPath string `json:"UserPath" yaml:"UserPath,omitempty"`
UserActive bool `json:"Active" yaml:"Active,omitempty"`
UserConfirmed bool `json:"Confirmed" yaml:"Confirmed,omitempty"`
RoleAdmin bool `json:"Admin" yaml:"Admin,omitempty"`
RoleGuest bool `json:"Guest" yaml:"Guest,omitempty"`
RoleChild bool `json:"Child" yaml:"Child,omitempty"`
RoleFamily bool `json:"Family" yaml:"Family,omitempty"`
RoleFriend bool `json:"Friend" yaml:"Friend,omitempty"`
IsArtist bool `json:"Artist" yaml:"Artist,omitempty"`
IsSubject bool `json:"Subject" yaml:"Subject,omitempty"`
CanEdit bool `json:"CanEdit" yaml:"CanEdit,omitempty"`
CanComment bool `json:"CanComment" yaml:"CanComment,omitempty"`
CanUpload bool `json:"CanUpload" yaml:"CanUpload,omitempty"`
CanDownload bool `json:"CanDownload" yaml:"CanDownload,omitempty"`
WebDAV bool `gorm:"column:webdav" json:"WebDAV" yaml:"WebDAV,omitempty"`
ApiToken string `json:"ApiToken" yaml:"ApiToken,omitempty"`
BirthYear int `json:"BirthYear" yaml:"BirthYear,omitempty"`
BirthMonth int `json:"BirthMonth" yaml:"BirthMonth,omitempty"`
BirthDay int `json:"BirthDay" yaml:"BirthDay,omitempty"`
LoginAttempts int `json:"-" yaml:"-,omitempty"`
LoginAt *time.Time `json:"-" yaml:"-"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
ID int `gorm:"primary_key" json:"ID" yaml:"-"`
PersonUID string `gorm:"type:varbinary(42);unique_index;" json:"UID" yaml:"UID"`
ParentUID string `gorm:"type:varbinary(42);" json:"ParentUID" yaml:"ParentUID,omitempty"`
UserUUID string `gorm:"type:varbinary(42);index;" json:"UserUUID" yaml:"UserUUID,omitempty"`
UserName string `gorm:"size:64;" json:"UserName" yaml:"UserName,omitempty"`
UserLocale string `gorm:"size:64;" json:"UserLocale" yaml:"UserLocale,omitempty"`
TimeZone string `gorm:"size:255;" json:"TimeZone" yaml:"TimeZone,omitempty"`
PrimaryEmail string `gorm:"size:255;index;" json:"PrimaryEmail" yaml:"PrimaryEmail,omitempty"`
BackupEmail string `gorm:"size:255;" json:"BackupEmail" yaml:"BackupEmail,omitempty"`
DisplayName string `gorm:"size:255;" json:"DisplayName" yaml:"DisplayName,omitempty"`
DisplayLocation string `gorm:"size:255;" json:"DisplayLocation" yaml:"DisplayLocation,omitempty"`
DisplayBio string `gorm:"type:text;" json:"DisplayBio" yaml:"DisplayBio,omitempty"`
NamePrefix string `gorm:"size:64;" json:"NamePrefix" yaml:"NamePrefix,omitempty"`
GivenName string `gorm:"size:128;" json:"GivenName" yaml:"GivenName,omitempty"`
FamilyName string `gorm:"size:128;" json:"FamilyName" yaml:"FamilyName,omitempty"`
NameSuffix string `gorm:"size:64;" json:"NameSuffix" yaml:"NameSuffix,omitempty"`
AvatarUID string `gorm:"type:varbinary(42);" json:"AvatarUID" yaml:"AvatarUID,omitempty"`
AvatarURL string `gorm:"size:255;" json:"AvatarURL" yaml:"AvatarURL,omitempty"`
FeedURL string `gorm:"size:255;" json:"FeedURL" yaml:"FeedURL,omitempty"`
FeedType string `gorm:"size:64" json:"FeedType" yaml:"FeedType,omitempty"`
FeedFollow bool `json:"FeedFollow" yaml:"FeedFollow,omitempty"`
BlogURL string `gorm:"size:255;" json:"BlogURL" yaml:"BlogURL,omitempty"`
BlogType string `gorm:"size:64;" json:"BlogType" yaml:"BlogType,omitempty"`
BlogFollow bool `json:"BlogFollow" yaml:"BlogFollow,omitempty"`
CompanyURL string `gorm:"size:255;" json:"CompanyURL" yaml:"CompanyURL,omitempty"`
CompanyName string `gorm:"size:128;" json:"CompanyName" yaml:"CompanyName,omitempty"`
CompanyPhone string `gorm:"size:255;" json:"CompanyPhone" yaml:"CompanyPhone,omitempty"`
PrimaryPhone string `gorm:"size:255;" json:"PrimaryPhone" yaml:"PrimaryPhone,omitempty"`
DepartmentName string `gorm:"size:255;" json:"DepartmentName" yaml:"DepartmentName,omitempty"`
JobTitle string `gorm:"size:255;" json:"JobTitle" yaml:"JobTitle,omitempty"`
AddressLat float32 `gorm:"type:FLOAT;index;" json:"AddressLat" yaml:"AddressLat,omitempty"`
AddressLng float32 `gorm:"type:FLOAT;index;" json:"AddressLng" yaml:"AddressLng,omitempty"`
AddressLine1 string `gorm:"size:255;" json:"AddressLine1" yaml:"AddressLine1,omitempty"`
AddressLine2 string `gorm:"size:255;" json:"AddressLine2" yaml:"AddressLine2,omitempty"`
AddressZip string `gorm:"size:255;" json:"AddressZip" yaml:"AddressZip,omitempty"`
AddressCity string `gorm:"size:255;" json:"AddressCity" yaml:"AddressCity,omitempty"`
AddressState string `gorm:"size:255;" json:"AddressState" yaml:"AddressState,omitempty"`
AddressCountry string `gorm:"type:varbinary(2);default:'zz'" json:"AddressCountry" yaml:"AddressCountry,omitempty"`
BirthYear int `json:"BirthYear" yaml:"BirthYear,omitempty"`
BirthMonth int `json:"BirthMonth" yaml:"BirthMonth,omitempty"`
BirthDay int `json:"BirthDay" yaml:"BirthDay,omitempty"`
TermsAccepted bool `json:"TermsAccepted" yaml:"TermsAccepted,omitempty"`
IsActive bool `json:"IsActive" yaml:"IsActive,omitempty"`
IsConfirmed bool `json:"IsConfirmed" yaml:"IsConfirmed,omitempty"`
IsPro bool `json:"IsPro" yaml:"IsPro,omitempty"`
IsSponsor bool `json:"IsSponsor" yaml:"IsSponsor,omitempty"`
IsContributor bool `json:"IsContributor" yaml:"IsContributor,omitempty"`
IsArtist bool `json:"IsArtist" yaml:"IsArtist,omitempty"`
IsSubject bool `json:"IsSubject" yaml:"IsSubject,omitempty"`
RoleAdmin bool `json:"RoleAdmin" yaml:"RoleAdmin,omitempty"`
RoleGuest bool `json:"RoleGuest" yaml:"RoleGuest,omitempty"`
RoleChild bool `json:"RoleChild" yaml:"RoleChild,omitempty"`
RoleFamily bool `json:"RoleFamily" yaml:"RoleFamily,omitempty"`
RoleFriend bool `json:"RoleFriend" yaml:"RoleFriend,omitempty"`
CanEdit bool `json:"CanEdit" yaml:"CanEdit,omitempty"`
CanDelete bool `json:"CanDelete" yaml:"CanDelete,omitempty"`
CanIndex bool `json:"CanIndex" yaml:"CanIndex,omitempty"`
CanShare bool `json:"CanShare" yaml:"CanShare,omitempty"`
CanComment bool `json:"CanComment" yaml:"CanComment,omitempty"`
CanUpload bool `json:"CanUpload" yaml:"CanUpload,omitempty"`
CanDownload bool `json:"CanDownload" yaml:"CanDownload,omitempty"`
HideLabels bool `json:"HideLabels" yaml:"HideLabels,omitempty"`
HidePlaces bool `json:"HidePlaces" yaml:"HidePlaces,omitempty"`
HidePeople bool `json:"HidePeople" yaml:"HidePeople,omitempty"`
HidePrivate bool `json:"HidePrivate" yaml:"HidePrivate,omitempty"`
HideLibrary bool `json:"HideLibrary" yaml:"HideLibrary,omitempty"`
HideSettings bool `json:"HideSettings" yaml:"HideSettings,omitempty"`
WebDAV bool `gorm:"column:webdav" json:"WebDAV" yaml:"WebDAV,omitempty"`
StoragePath string `gorm:"column:storage_path;size:255;" json:"StoragePath" yaml:"StoragePath,omitempty"`
ApiToken string `gorm:"column:api_token;size:255;" json:"ApiToken" yaml:"ApiToken,omitempty"`
ApiSecret string `gorm:"column:api_secret;size:255;" json:"-" yaml:"-"`
AmazonID string `gorm:"column:amazon_id;size:255;" json:"AmazonID" yaml:"AmazonID,omitempty"`
AppleID string `gorm:"column:apple_id;size:255;" json:"AppleID" yaml:"AppleID,omitempty"`
EyeEmID string `gorm:"column:eyeem_id;size:255;" json:"EyeEmID" yaml:"EyeEmID,omitempty"`
FacebookID string `gorm:"column:facebook_id;size:255;" json:"FacebookID" yaml:"FacebookID,omitempty"`
FlickrID string `gorm:"column:flickr_id;size:255;" json:"FlickrID" yaml:"FlickrID,omitempty"`
GitHubID string `gorm:"column:github_id;size:255;" json:"GitHubID" yaml:"GitHubID,omitempty"`
GitLabID string `gorm:"column:gitlab_id;size:255;" json:"GitLabID" yaml:"GitLabID,omitempty"`
GoogleID string `gorm:"column:google_id;size:255;" json:"GoogleID" yaml:"GoogleID,omitempty"`
InstagramID string `gorm:"column:instagram_id;size:255;" json:"InstagramID" yaml:"InstagramID,omitempty"`
LinkedinID string `gorm:"column:linkedin_id;size:255;" json:"LinkedinID" yaml:"LinkedinID,omitempty"`
MastodonID string `gorm:"column:mastodon_id;size:255;" json:"MastodonID" yaml:"MastodonID,omitempty"`
NextcloudID string `gorm:"column:nextcloud_id;size:255;" json:"NextcloudID" yaml:"NextcloudID,omitempty"`
TelegramID string `gorm:"column:telegram_id;size:255;" json:"TelegramID" yaml:"TelegramID,omitempty"`
TwitterID string `gorm:"column:twitter_id;size:255;" json:"TwitterID" yaml:"TwitterID,omitempty"`
WhatsAppID string `gorm:"column:whatsapp_id;size:255;" json:"WhatsAppID" yaml:"WhatsAppID,omitempty"`
YouTubeID string `gorm:"column:youtube_id;size:255;" json:"YouTubeID" yaml:"YouTubeID,omitempty"`
UserNotes string `gorm:"type:text;" json:"UserNotes" yaml:"UserNotes,omitempty"`
LoginAttempts int `json:"-" yaml:"-,omitempty"`
LoginAt *time.Time `json:"-" yaml:"-"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
}
// TableName the database table name.
func (Person) TableName() string {
return "people"
}
// Default admin user.
var Admin = Person{
ID: 1,
UserName: "admin",
DisplayName: "Admin",
RoleAdmin: true,
UserActive: true,
UserConfirmed: true,
ID: 1,
UserName: "admin",
DisplayName: "Admin",
RoleAdmin: true,
IsActive: true,
IsConfirmed: true,
}
// Anonymous, public user without own account.
var UnknownPerson = Person{
ID: -1,
PersonUID: "u000000000000001",
UserName: "",
DisplayName: "Anonymous",
RoleAdmin: false,
RoleGuest: false,
UserActive: false,
UserConfirmed: false,
ID: -1,
PersonUID: "u000000000000001",
UserName: "",
DisplayName: "Anonymous",
RoleAdmin: false,
RoleGuest: false,
IsActive: false,
IsConfirmed: false,
}
// Guest user without own account for link sharing.
var Guest = Person{
ID: -2,
PersonUID: "u000000000000002",
UserName: "",
DisplayName: "Guest",
RoleAdmin: false,
RoleGuest: true,
UserActive: false,
UserConfirmed: false,
ID: -2,
PersonUID: "u000000000000002",
UserName: "",
DisplayName: "Guest",
RoleAdmin: false,
RoleGuest: true,
IsActive: false,
IsConfirmed: false,
}
// CreateDefaultUsers initializes the database with default user accounts.

View file

@ -24,8 +24,10 @@ type Location struct {
const ApiName = "photoprism places"
var ReverseLookupURL = "https://places.photoprism.org/v1/location/%s"
var client = &http.Client{Timeout: 60 * time.Second} // TODO: Change timeout if needed
var Key = ""
var UserAgent = "PhotoPrism/DEVELOP"
var ReverseLookupURL = "https://places.photoprism.pro/v1/location/%s?key=%s"
var client = &http.Client{Timeout: 60 * time.Second}
func NewLocation(id string, lat, lng float64, name, category string, place Place, cached bool) *Location {
result := &Location{
@ -63,7 +65,7 @@ func FindLocation(id string) (result Location, err error) {
}
}
url := fmt.Sprintf(ReverseLookupURL, id)
url := fmt.Sprintf(ReverseLookupURL, id, Key)
log.Debugf("api: sending request to %s (%s)", url, ApiName)
@ -74,6 +76,8 @@ func FindLocation(id string) (result Location, err error) {
return result, err
}
req.Header.Set("User-Agent", UserAgent)
var r *http.Response
for i := 0; i < 3; i++ {