Skip to content

Commit

Permalink
feat: allow per-user settings and store them in database (#639)
Browse files Browse the repository at this point in the history
* create needed field in sqlite database

* update account model

* update Account struct for save Account options

* update sqlite database return account settings

* save configure in sqlite as text and return that

* read configure from user account and defualt configure for shiori

* add api/ui for update settings in database user can save settings in database (in sqlite database)

* check configures be in json format before save in database

* support MariaDB

* fix wrong comment

* support PostgreSQL

* revert unneeded change in new logic

* change configures to config

* change SaveAccount to SaveAccountSettings

* add migrate database scripts

* change default in migration scrtipts

* update model

* read config field as json from database

* fix parse value config value & update config update

* update default value for new user

* update settings variable name to reflect database value in UI

* fix typo

* not panic if user not exist and update worng comment

* visitor user can update there settings now

* remove unneeded loading dialog

* fix typo

* update function for pg and mysql

* remove IsJson

* move scan method to model

* simplify jsonify

* simplify assignees value to account.Config

* missing part of function

* fix some typo and unneeded field in struct

* add down migrate script for all database

* change createEbook to CreateEbook

* use json instead of text in mysql and postgres

* implement

* remove unneeded part

* remove unneeded jsonify in code

* return SelectContext and GetContext

* remove defualt config in reques for new user it will be set in backend

* New API

* remove legacy API

* remove validateSessionWithoutOwnerStatus

* remove Jsonify function don't need that anymore

* add unit test for database

* update migrate script name

* change put to patch

* return PUT

* fix Patch problem and now use PATCH instead of PUT

* remove unneeded retuen

* more cleaner code for request new settings

* fix bug to handle string in Scan method thanks to fmartingr

* fix Authorization & use GetAccount & remove username from request

* shiori-settings remove and it read from shiori-account

* add swagger documentation

* API unit test

* fix typo

* remove unneeded coment

Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com>

* better Documentation

Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com>

* shiori-toke remove on logout

* fix typo

* add unit test check update config in database

* update swag documentation

* fix swag formaing error

---------

Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com>
  • Loading branch information
Monirzadeh and fmartingr committed Oct 15, 2023
1 parent 46c1a6a commit c05d617
Show file tree
Hide file tree
Showing 29 changed files with 906 additions and 117 deletions.
72 changes: 72 additions & 0 deletions docs/swagger/docs.go
Expand Up @@ -15,6 +15,38 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/v1/auth/account": {
"patch": {
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "Perform actions on the currently logged-in user.",
"parameters": [
{
"description": "Config data",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/api_v1.settingRequestPayload"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Account"
}
},
"403": {
"description": "Token not provided/invalid"
}
}
}
},
"/api/v1/auth/login": {
"post": {
"consumes": [
Expand Down Expand Up @@ -175,9 +207,20 @@ const docTemplate = `{
}
}
},
"api_v1.settingRequestPayload": {
"type": "object",
"properties": {
"config": {
"$ref": "#/definitions/model.UserConfig"
}
}
},
"model.Account": {
"type": "object",
"properties": {
"config": {
"$ref": "#/definitions/model.UserConfig"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -205,6 +248,35 @@ const docTemplate = `{
"type": "string"
}
}
},
"model.UserConfig": {
"type": "object",
"properties": {
"HideExcerpt": {
"type": "boolean"
},
"HideThumbnail": {
"type": "boolean"
},
"KeepMetadata": {
"type": "boolean"
},
"ListMode": {
"type": "boolean"
},
"MakePublic": {
"type": "boolean"
},
"NightMode": {
"type": "boolean"
},
"ShowId": {
"type": "boolean"
},
"UseArchive": {
"type": "boolean"
}
}
}
}
}`
Expand Down
72 changes: 72 additions & 0 deletions docs/swagger/swagger.json
Expand Up @@ -4,6 +4,38 @@
"contact": {}
},
"paths": {
"/api/v1/auth/account": {
"patch": {
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "Perform actions on the currently logged-in user.",
"parameters": [
{
"description": "Config data",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/api_v1.settingRequestPayload"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Account"
}
},
"403": {
"description": "Token not provided/invalid"
}
}
}
},
"/api/v1/auth/login": {
"post": {
"consumes": [
Expand Down Expand Up @@ -164,9 +196,20 @@
}
}
},
"api_v1.settingRequestPayload": {
"type": "object",
"properties": {
"config": {
"$ref": "#/definitions/model.UserConfig"
}
}
},
"model.Account": {
"type": "object",
"properties": {
"config": {
"$ref": "#/definitions/model.UserConfig"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -194,6 +237,35 @@
"type": "string"
}
}
},
"model.UserConfig": {
"type": "object",
"properties": {
"HideExcerpt": {
"type": "boolean"
},
"HideThumbnail": {
"type": "boolean"
},
"KeepMetadata": {
"type": "boolean"
},
"ListMode": {
"type": "boolean"
},
"MakePublic": {
"type": "boolean"
},
"NightMode": {
"type": "boolean"
},
"ShowId": {
"type": "boolean"
},
"UseArchive": {
"type": "boolean"
}
}
}
}
}
46 changes: 46 additions & 0 deletions docs/swagger/swagger.yaml
Expand Up @@ -22,8 +22,15 @@ definitions:
token:
type: string
type: object
api_v1.settingRequestPayload:
properties:
config:
$ref: '#/definitions/model.UserConfig'
type: object
model.Account:
properties:
config:
$ref: '#/definitions/model.UserConfig'
id:
type: integer
owner:
Expand All @@ -42,9 +49,48 @@ definitions:
name:
type: string
type: object
model.UserConfig:
properties:
HideExcerpt:
type: boolean
HideThumbnail:
type: boolean
KeepMetadata:
type: boolean
ListMode:
type: boolean
MakePublic:
type: boolean
NightMode:
type: boolean
ShowId:
type: boolean
UseArchive:
type: boolean
type: object
info:
contact: {}
paths:
/api/v1/auth/account:
patch:
parameters:
- description: Config data
in: body
name: payload
schema:
$ref: '#/definitions/api_v1.settingRequestPayload'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Account'
"403":
description: Token not provided/invalid
summary: Perform actions on the currently logged-in user.
tags:
- Auth
/api/v1/auth/login:
post:
consumes:
Expand Down
3 changes: 3 additions & 0 deletions internal/database/database.go
Expand Up @@ -89,6 +89,9 @@ type DB interface {
// SaveAccount saves new account in database
SaveAccount(ctx context.Context, a model.Account) error

// SaveAccountSettings saves settings for specific user in database
SaveAccountSettings(ctx context.Context, a model.Account) error

// GetAccounts fetch list of account (without its password) with matching keyword.
GetAccounts(ctx context.Context, opts GetAccountsOptions) ([]model.Account, error)

Expand Down
8 changes: 4 additions & 4 deletions internal/database/migrations/mysql/0001_initial.up.sql
@@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS account(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(250) NOT NULL,
password BINARY(80) NOT NULL,
owner TINYINT(1) NOT NULL DEFAULT '0',
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(250) NOT NULL,
password BINARY(80) NOT NULL,
owner TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (id),
UNIQUE KEY account_username_UNIQUE (username))
CHARACTER SET utf8mb4;
1 change: 1 addition & 0 deletions internal/database/migrations/mysql/0005_config.down.sql
@@ -0,0 +1 @@
ALTER TABLE account DROP COLUMN config;
2 changes: 2 additions & 0 deletions internal/database/migrations/mysql/0005_config.up.sql
@@ -0,0 +1,2 @@
ALTER TABLE account
ADD COLUMN config JSON NOT NULL DEFAULT '{}';
2 changes: 1 addition & 1 deletion internal/database/migrations/postgres/0001_initial.up.sql
Expand Up @@ -33,4 +33,4 @@ CREATE TABLE IF NOT EXISTS bookmark_tag(
CONSTRAINT bookmark_tag_tag_id_FK FOREIGN KEY (tag_id) REFERENCES tag (id));

CREATE INDEX IF NOT EXISTS bookmark_tag_bookmark_id_FK ON bookmark_tag (bookmark_id);
CREATE INDEX IF NOT EXISTS bookmark_tag_tag_id_FK ON bookmark_tag (tag_id);
CREATE INDEX IF NOT EXISTS bookmark_tag_tag_id_FK ON bookmark_tag (tag_id);
1 change: 1 addition & 0 deletions internal/database/migrations/postgres/0002_config.down.sql
@@ -0,0 +1 @@
ALTER TABLE account DROP COLUMN config;
2 changes: 2 additions & 0 deletions internal/database/migrations/postgres/0002_config.up.sql
@@ -0,0 +1,2 @@
ALTER TABLE account
ADD COLUMN config JSONB NOT NULL DEFAULT '{}';
1 change: 1 addition & 0 deletions internal/database/migrations/sqlite/0003_config.down.sql
@@ -0,0 +1 @@
ALTER TABLE account DROP COLUMN config;
3 changes: 3 additions & 0 deletions internal/database/migrations/sqlite/0003_config.up.sql
@@ -0,0 +1,3 @@
ALTER TABLE account
ADD config JSON NOT NULL DEFAULT '{}';

19 changes: 15 additions & 4 deletions internal/database/mysql.go
Expand Up @@ -532,11 +532,22 @@ func (db *MySQLDatabase) SaveAccount(ctx context.Context, account model.Account)

// Insert account to database
_, err = db.ExecContext(ctx, `INSERT INTO account
(username, password, owner) VALUES (?, ?, ?)
(username, password, owner, config) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
password = VALUES(password),
owner = VALUES(owner)`,
account.Username, hashedPassword, account.Owner)
account.Username, hashedPassword, account.Owner, account.Config)

return errors.WithStack(err)
}

// SaveAccountSettings update settings for specific account in database. Returns error if any happened
func (db *MySQLDatabase) SaveAccountSettings(ctx context.Context, account model.Account) (err error) {
// Update account config in database for specific user
_, err = db.ExecContext(ctx, `UPDATE account
SET config = ?
WHERE username = ?`,
account.Config, account.Username)

return errors.WithStack(err)
}
Expand All @@ -545,7 +556,7 @@ func (db *MySQLDatabase) SaveAccount(ctx context.Context, account model.Account)
func (db *MySQLDatabase) GetAccounts(ctx context.Context, opts GetAccountsOptions) ([]model.Account, error) {
// Create query
args := []interface{}{}
query := `SELECT id, username, owner FROM account WHERE 1`
query := `SELECT id, username, owner, config FROM account WHERE 1`

if opts.Keyword != "" {
query += " AND username LIKE ?"
Expand Down Expand Up @@ -573,7 +584,7 @@ func (db *MySQLDatabase) GetAccounts(ctx context.Context, opts GetAccountsOption
func (db *MySQLDatabase) GetAccount(ctx context.Context, username string) (model.Account, bool, error) {
account := model.Account{}
if err := db.GetContext(ctx, &account, `SELECT
id, username, password, owner FROM account WHERE username = ?`,
id, username, password, owner, config FROM account WHERE username = ?`,
username,
); err != nil {
return account, false, errors.WithStack(err)
Expand Down

0 comments on commit c05d617

Please sign in to comment.