mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-25 12:53:34 +00:00
Version 3 starter
This commit is contained in:
82
backend/internal/entity/auth/methods.go
Normal file
82
backend/internal/entity/auth/methods.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
)
|
||||
|
||||
// GetByID finds a auth by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// GetByUserIDType finds a user by email
|
||||
func GetByUserIDType(userID int, authType string) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByUserIDType(userID, authType)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a Auth from this model
|
||||
func Create(auth *Model) (int, error) {
|
||||
if auth.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create auth when model already has an ID")
|
||||
}
|
||||
|
||||
auth.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
user_id,
|
||||
type,
|
||||
secret,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:user_id,
|
||||
:type,
|
||||
:secret,
|
||||
:is_deleted
|
||||
)`, auth)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a Auth from this model
|
||||
func Update(auth *Model) error {
|
||||
if auth.ID == 0 {
|
||||
return goerrors.New("Cannot update auth when model doesn't have an ID")
|
||||
}
|
||||
|
||||
auth.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
user_id = :user_id,
|
||||
type = :type,
|
||||
secret = :secret,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, auth)
|
||||
|
||||
return err
|
||||
}
|
||||
98
backend/internal/entity/auth/model.go
Normal file
98
backend/internal/entity/auth/model.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "auth"
|
||||
|
||||
// TypePassword is the Password Type
|
||||
TypePassword = "password"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id"`
|
||||
UserID int `json:"user_id" db:"user_id"`
|
||||
Type string `json:"type" db:"type"`
|
||||
Secret string `json:"secret,omitempty" db:"secret"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// LoadByUserIDType will load from an ID
|
||||
func (m *Model) LoadByUserIDType(userID int, authType string) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE user_id = ? AND type = ? LIMIT 1", tableName)
|
||||
params := []interface{}{userID, authType}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPassword will generate a hashed password based on given string
|
||||
func (m *Model) SetPassword(password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost+2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Type = TypePassword
|
||||
m.Secret = string(hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSecret will check if a given secret matches the encrypted secret
|
||||
func (m *Model) ValidateSecret(secret string) error {
|
||||
if m.Type != TypePassword {
|
||||
return goerrors.New("Could not validate Secret, auth type is not a Password")
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(m.Secret), []byte(secret))
|
||||
if err != nil {
|
||||
return goerrors.New("Invalid Password")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
backend/internal/entity/certificate/filters.go
Normal file
25
backend/internal/entity/certificate/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
173
backend/internal/entity/certificate/methods.go
Normal file
173
backend/internal/entity/certificate/methods.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a row by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a row from this model
|
||||
func Create(certificate *Model) (int, error) {
|
||||
if certificate.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create certificate when model already has an ID")
|
||||
}
|
||||
|
||||
certificate.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
user_id,
|
||||
type,
|
||||
certificate_authority_id,
|
||||
dns_provider_id,
|
||||
name,
|
||||
domain_names,
|
||||
expires_on,
|
||||
status,
|
||||
meta,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:user_id,
|
||||
:type,
|
||||
:certificate_authority_id,
|
||||
:dns_provider_id,
|
||||
:name,
|
||||
:domain_names,
|
||||
:expires_on,
|
||||
:status,
|
||||
:meta,
|
||||
:is_deleted
|
||||
)`, certificate)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a Auth from this model
|
||||
func Update(certificate *Model) error {
|
||||
if certificate.ID == 0 {
|
||||
return goerrors.New("Cannot update certificate when model doesn't have an ID")
|
||||
}
|
||||
|
||||
certificate.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
type = :type,
|
||||
user_id = :user_id,
|
||||
certificate_authority_id = :certificate_authority_id,
|
||||
dns_provider_id = :dns_provider_id,
|
||||
name = :name,
|
||||
domain_names = :domain_names,
|
||||
expires_on = :expires_on,
|
||||
status = :status,
|
||||
meta = :meta,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, certificate)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of certificates
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetByStatus will select rows that are ready for requesting
|
||||
func GetByStatus(status string) ([]Model, error) {
|
||||
models := make([]Model, 0)
|
||||
db := database.GetInstance()
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
t.*
|
||||
FROM "%s" t
|
||||
INNER JOIN "dns_provider" d ON d."id" = t."dns_provider_id"
|
||||
INNER JOIN "certificate_authority" c ON c."id" = t."certificate_authority_id"
|
||||
WHERE
|
||||
t."type" IN ("http", "dns") AND
|
||||
t."status" = ? AND
|
||||
t."certificate_authority_id" > 0 AND
|
||||
t."dns_provider_id" > 0 AND
|
||||
t."is_deleted" = 0
|
||||
`, tableName)
|
||||
|
||||
params := []interface{}{StatusReady}
|
||||
err := db.Select(&models, query, params...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
logger.Error("GetByStatusError", err)
|
||||
logger.Debug("Query: %s -- %+v", query, params)
|
||||
}
|
||||
|
||||
return models, err
|
||||
}
|
||||
178
backend/internal/entity/certificate/model.go
Normal file
178
backend/internal/entity/certificate/model.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity/certificateauthority"
|
||||
"npm/internal/entity/dnsprovider"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "certificate"
|
||||
|
||||
// TypeCustom ...
|
||||
TypeCustom = "custom"
|
||||
// TypeHTTP ...
|
||||
TypeHTTP = "http"
|
||||
// TypeDNS ...
|
||||
TypeDNS = "dns"
|
||||
// TypeMkcert ...
|
||||
TypeMkcert = "mkcert"
|
||||
// StatusReady is ready for certificate to be requested
|
||||
StatusReady = "ready"
|
||||
// StatusRequesting is process of being requested
|
||||
StatusRequesting = "requesting"
|
||||
// StatusFailed is a certicifate that failed to request
|
||||
StatusFailed = "failed"
|
||||
// StatusProvided is a certificate provided and ready for actual use
|
||||
StatusProvided = "provided"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
ExpiresOn types.NullableDBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"`
|
||||
Type string `json:"type" db:"type" filter:"type,string"`
|
||||
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
|
||||
CertificateAuthorityID int `json:"certificate_authority_id" db:"certificate_authority_id" filter:"certificate_authority_id,integer"`
|
||||
DNSProviderID int `json:"dns_provider_id" db:"dns_provider_id" filter:"dns_provider_id,integer"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"`
|
||||
Status string `json:"status" db:"status" filter:"status,string"`
|
||||
ErrorMessage string `json:"error_message,omitempty" db:"error_message" filter:"error_message,string"`
|
||||
Meta types.JSONB `json:"-" db:"meta"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
// Expansions:
|
||||
CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"`
|
||||
DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, 0}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.UserID == 0 {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
if !m.Validate() {
|
||||
return fmt.Errorf("Certificate data is incorrect or incomplete for this type")
|
||||
}
|
||||
|
||||
m.setDefaultStatus()
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a certificate as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Validate will make sure the data given is expected. This object is a bit complicated,
|
||||
// as there could be multiple combinations of values.
|
||||
func (m *Model) Validate() bool {
|
||||
switch m.Type {
|
||||
case TypeCustom:
|
||||
// TODO: make sure meta contains required fields
|
||||
return m.DNSProviderID == 0 && m.CertificateAuthorityID == 0
|
||||
|
||||
case TypeHTTP:
|
||||
return m.DNSProviderID == 0 && m.CertificateAuthorityID > 0
|
||||
|
||||
case TypeDNS:
|
||||
return m.DNSProviderID > 0 && m.CertificateAuthorityID > 0
|
||||
|
||||
case TypeMkcert:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) setDefaultStatus() {
|
||||
if m.ID == 0 {
|
||||
// It's a new certificate
|
||||
if m.Type == TypeCustom {
|
||||
m.Status = StatusProvided
|
||||
} else {
|
||||
m.Status = StatusReady
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expand will populate attached objects for the model
|
||||
func (m *Model) Expand() {
|
||||
if m.CertificateAuthorityID > 0 {
|
||||
certificateAuthority, _ := certificateauthority.GetByID(m.CertificateAuthorityID)
|
||||
m.CertificateAuthority = &certificateAuthority
|
||||
}
|
||||
if m.DNSProviderID > 0 {
|
||||
dnsProvider, _ := dnsprovider.GetByID(m.DNSProviderID)
|
||||
m.DNSProvider = &dnsProvider
|
||||
}
|
||||
}
|
||||
|
||||
// Request ...
|
||||
func (m *Model) Request() error {
|
||||
m.Expand()
|
||||
m.Status = StatusRequesting
|
||||
if err := m.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If error
|
||||
m.Status = StatusFailed
|
||||
m.ErrorMessage = "something"
|
||||
if err := m.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If done
|
||||
m.Status = StatusProvided
|
||||
t := time.Now()
|
||||
m.ExpiresOn.Time = &t
|
||||
if err := m.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
backend/internal/entity/certificate/structs.go
Normal file
15
backend/internal/entity/certificate/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for users list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
25
backend/internal/entity/certificateauthority/filters.go
Normal file
25
backend/internal/entity/certificateauthority/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package certificateauthority
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
125
backend/internal/entity/certificateauthority/methods.go
Normal file
125
backend/internal/entity/certificateauthority/methods.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package certificateauthority
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a row by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a row from this model
|
||||
func Create(ca *Model) (int, error) {
|
||||
if ca.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create certificate authority when model already has an ID")
|
||||
}
|
||||
|
||||
ca.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
name,
|
||||
acme2_url,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:name,
|
||||
:acme2_url,
|
||||
:is_deleted
|
||||
)`, ca)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a row from this model
|
||||
func Update(ca *Model) error {
|
||||
if ca.ID == 0 {
|
||||
return goerrors.New("Cannot update certificate authority when model doesn't have an ID")
|
||||
}
|
||||
|
||||
ca.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
name = :name,
|
||||
acme2_url = :acme2_url,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, ca)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of certificates
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Error("ListCertificateAuthoritiesError", queryErr)
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Error("ListCertificateAuthoritiesError", err)
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
67
backend/internal/entity/certificateauthority/model.go
Normal file
67
backend/internal/entity/certificateauthority/model.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package certificateauthority
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "certificate_authority"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
Acme2URL string `json:"acme2_url" db:"acme2_url" filter:"acme2_url,string"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, 0}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a certificate as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
15
backend/internal/entity/certificateauthority/structs.go
Normal file
15
backend/internal/entity/certificateauthority/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package certificateauthority
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for users list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
25
backend/internal/entity/dnsprovider/filters.go
Normal file
25
backend/internal/entity/dnsprovider/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package dnsprovider
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
131
backend/internal/entity/dnsprovider/methods.go
Normal file
131
backend/internal/entity/dnsprovider/methods.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package dnsprovider
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a row by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a row from this model
|
||||
func Create(provider *Model) (int, error) {
|
||||
if provider.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create dns provider when model already has an ID")
|
||||
}
|
||||
|
||||
provider.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
user_id,
|
||||
provider_key,
|
||||
name,
|
||||
meta,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:user_id,
|
||||
:provider_key,
|
||||
:name,
|
||||
:meta,
|
||||
:is_deleted
|
||||
)`, provider)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a row from this model
|
||||
func Update(provider *Model) error {
|
||||
if provider.ID == 0 {
|
||||
return goerrors.New("Cannot update dns provider when model doesn't have an ID")
|
||||
}
|
||||
|
||||
provider.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
user_id = :user_id,
|
||||
provider_key = :provider_key,
|
||||
name = :name,
|
||||
meta = :meta,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, provider)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of certificates
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Error("ListDnsProvidersError", queryErr)
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Error("ListDnsProvidersError", err)
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
73
backend/internal/entity/dnsprovider/model.go
Normal file
73
backend/internal/entity/dnsprovider/model.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package dnsprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "dns_provider"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
|
||||
ProviderKey string `json:"provider_key" db:"provider_key" filter:"provider_key,string"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
Meta types.JSONB `json:"meta" db:"meta"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, 0}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.UserID == 0 {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a certificate as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
15
backend/internal/entity/dnsprovider/structs.go
Normal file
15
backend/internal/entity/dnsprovider/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package dnsprovider
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for the list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
158
backend/internal/entity/filters.go
Normal file
158
backend/internal/entity/filters.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// FilterMapFunction is a filter map function
|
||||
type FilterMapFunction func(value []string) []string
|
||||
|
||||
// FilterTagName ...
|
||||
const FilterTagName = "filter"
|
||||
|
||||
// DBTagName ...
|
||||
const DBTagName = "db"
|
||||
|
||||
// GenerateSQLFromFilters will return a Query and params for use as WHERE clause in SQL queries
|
||||
// This will use a AND where clause approach.
|
||||
func GenerateSQLFromFilters(filters []model.Filter, fieldMap map[string]string, fieldMapFunctions map[string]FilterMapFunction) (string, []interface{}) {
|
||||
clauses := make([]string, 0)
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
for _, filter := range filters {
|
||||
// Lookup this filter field from the functions map
|
||||
if _, ok := fieldMapFunctions[filter.Field]; ok {
|
||||
filter.Value = fieldMapFunctions[filter.Field](filter.Value)
|
||||
}
|
||||
|
||||
// Lookup this filter field from the name map
|
||||
if _, ok := fieldMap[filter.Field]; ok {
|
||||
filter.Field = fieldMap[filter.Field]
|
||||
}
|
||||
|
||||
// Special case for LIKE queries, the column needs to be uppercase for comparison
|
||||
fieldName := fmt.Sprintf("`%s`", filter.Field)
|
||||
if strings.ToLower(filter.Modifier) == "contains" || strings.ToLower(filter.Modifier) == "starts" || strings.ToLower(filter.Modifier) == "ends" {
|
||||
fieldName = fmt.Sprintf("UPPER(`%s`)", filter.Field)
|
||||
}
|
||||
|
||||
clauses = append(clauses, fmt.Sprintf("%s %s", fieldName, getSQLAssignmentFromModifier(filter, ¶ms)))
|
||||
}
|
||||
|
||||
return strings.Join(clauses, " AND "), params
|
||||
}
|
||||
|
||||
func getSQLAssignmentFromModifier(filter model.Filter, params *[]interface{}) string {
|
||||
var clause string
|
||||
|
||||
// Quick hacks
|
||||
if filter.Modifier == "in" && len(filter.Value) == 1 {
|
||||
filter.Modifier = "equals"
|
||||
} else if filter.Modifier == "notin" && len(filter.Value) == 1 {
|
||||
filter.Modifier = "not"
|
||||
}
|
||||
|
||||
switch strings.ToLower(filter.Modifier) {
|
||||
default:
|
||||
clause = "= ?"
|
||||
case "not":
|
||||
clause = "!= ?"
|
||||
case "min":
|
||||
clause = ">= ?"
|
||||
case "max":
|
||||
clause = "<= ?"
|
||||
case "greater":
|
||||
clause = "> ?"
|
||||
case "lesser":
|
||||
clause = "< ?"
|
||||
|
||||
// LIKE modifiers:
|
||||
case "contains":
|
||||
*params = append(*params, strings.ToUpper(filter.Value[0]))
|
||||
return "LIKE '%' || ? || '%'"
|
||||
case "starts":
|
||||
*params = append(*params, strings.ToUpper(filter.Value[0]))
|
||||
return "LIKE ? || '%'"
|
||||
case "ends":
|
||||
*params = append(*params, strings.ToUpper(filter.Value[0]))
|
||||
return "LIKE '%' || ?"
|
||||
|
||||
// Array parameter modifiers:
|
||||
case "in":
|
||||
s, p := buildInArray(filter.Value)
|
||||
*params = append(*params, p...)
|
||||
return fmt.Sprintf("IN (%s)", s)
|
||||
case "notin":
|
||||
s, p := buildInArray(filter.Value)
|
||||
*params = append(*params, p...)
|
||||
return fmt.Sprintf("NOT IN (%s)", s)
|
||||
}
|
||||
|
||||
*params = append(*params, filter.Value[0])
|
||||
return clause
|
||||
}
|
||||
|
||||
// GetFilterMap ...
|
||||
func GetFilterMap(m interface{}) map[string]string {
|
||||
var filterMap = make(map[string]string)
|
||||
|
||||
// TypeOf returns the reflection Type that represents the dynamic type of variable.
|
||||
// If variable is a nil interface value, TypeOf returns nil.
|
||||
t := reflect.TypeOf(m)
|
||||
|
||||
// Iterate over all available fields and read the tag value
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
// Get the field, returns https://golang.org/pkg/reflect/#StructField
|
||||
field := t.Field(i)
|
||||
|
||||
// Get the field tag value
|
||||
filterTag := field.Tag.Get(FilterTagName)
|
||||
dbTag := field.Tag.Get(DBTagName)
|
||||
if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" {
|
||||
// Filter tag can be a 2 part thing: name,type
|
||||
// ie: account_id,integer
|
||||
// So we need to split and use the first part
|
||||
parts := strings.Split(filterTag, ",")
|
||||
filterMap[parts[0]] = dbTag
|
||||
filterMap[filterTag] = dbTag
|
||||
}
|
||||
}
|
||||
|
||||
return filterMap
|
||||
}
|
||||
|
||||
// GetDBColumns ...
|
||||
func GetDBColumns(m interface{}) []string {
|
||||
var columns []string
|
||||
t := reflect.TypeOf(m)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
dbTag := field.Tag.Get(DBTagName)
|
||||
if dbTag != "" && dbTag != "-" {
|
||||
columns = append(columns, dbTag)
|
||||
}
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
func buildInArray(items []string) (string, []interface{}) {
|
||||
// Query string placeholder
|
||||
strs := make([]string, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
strs[i] = "?"
|
||||
}
|
||||
|
||||
// Params as interface
|
||||
params := make([]interface{}, len(items))
|
||||
for i, v := range items {
|
||||
params[i] = v
|
||||
}
|
||||
|
||||
return strings.Join(strs, ", "), params
|
||||
}
|
||||
223
backend/internal/entity/filters_schema.go
Normal file
223
backend/internal/entity/filters_schema.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetFilterSchema creates a jsonschema for validating filters, based on the model
|
||||
// object given and by reading the struct "filter" tags.
|
||||
func GetFilterSchema(m interface{}) string {
|
||||
var schemas []string
|
||||
t := reflect.TypeOf(m)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
filterTag := field.Tag.Get(FilterTagName)
|
||||
|
||||
if filterTag != "" && filterTag != "-" {
|
||||
// split out tag value "field,filtreType"
|
||||
// with a default filter type of string
|
||||
items := strings.Split(filterTag, ",")
|
||||
if len(items) == 1 {
|
||||
items = append(items, "string")
|
||||
}
|
||||
|
||||
switch items[1] {
|
||||
case "int":
|
||||
fallthrough
|
||||
case "integer":
|
||||
schemas = append(schemas, intFieldSchema(items[0]))
|
||||
case "bool":
|
||||
fallthrough
|
||||
case "boolean":
|
||||
schemas = append(schemas, boolFieldSchema(items[0]))
|
||||
case "date":
|
||||
schemas = append(schemas, dateFieldSchema(items[0]))
|
||||
case "regex":
|
||||
if len(items) < 3 {
|
||||
items = append(items, ".*")
|
||||
}
|
||||
schemas = append(schemas, regexFieldSchema(items[0], items[2]))
|
||||
|
||||
default:
|
||||
schemas = append(schemas, stringFieldSchema(items[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newFilterSchema(schemas)
|
||||
}
|
||||
|
||||
// newFilterSchema is the main method to specify a new Filter Schema for use in Middleware
|
||||
func newFilterSchema(fieldSchemas []string) string {
|
||||
return fmt.Sprintf(baseFilterSchema, strings.Join(fieldSchemas, ", "))
|
||||
}
|
||||
|
||||
// boolFieldSchema returns the Field Schema for a Boolean accepted value field
|
||||
func boolFieldSchema(fieldName string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"pattern": "^%s$"
|
||||
},
|
||||
"modifier": %s,
|
||||
"value": {
|
||||
"oneOf": [
|
||||
%s,
|
||||
{
|
||||
"type": "array",
|
||||
"items": %s
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, fieldName, boolModifiers, filterBool, filterBool)
|
||||
}
|
||||
|
||||
// intFieldSchema returns the Field Schema for a Integer accepted value field
|
||||
func intFieldSchema(fieldName string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"pattern": "^%s$"
|
||||
},
|
||||
"modifier": %s,
|
||||
"value": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, fieldName, allModifiers)
|
||||
}
|
||||
|
||||
// stringFieldSchema returns the Field Schema for a String accepted value field
|
||||
func stringFieldSchema(fieldName string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"pattern": "^%s$"
|
||||
},
|
||||
"modifier": %s,
|
||||
"value": {
|
||||
"oneOf": [
|
||||
%s,
|
||||
{
|
||||
"type": "array",
|
||||
"items": %s
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, fieldName, stringModifiers, filterString, filterString)
|
||||
}
|
||||
|
||||
// regexFieldSchema returns the Field Schema for a String accepted value field matching a Regex
|
||||
func regexFieldSchema(fieldName string, regex string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"pattern": "^%s$"
|
||||
},
|
||||
"modifier": %s,
|
||||
"value": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "%s"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, fieldName, stringModifiers, regex, regex)
|
||||
}
|
||||
|
||||
// dateFieldSchema returns the Field Schema for a String accepted value field matching a Date format
|
||||
func dateFieldSchema(fieldName string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"pattern": "^%s$"
|
||||
},
|
||||
"modifier": %s,
|
||||
"value": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, fieldName, allModifiers)
|
||||
}
|
||||
|
||||
const allModifiers = `{
|
||||
"type": "string",
|
||||
"pattern": "^(equals|not|contains|starts|ends|in|notin|min|max|greater|less)$"
|
||||
}`
|
||||
|
||||
const boolModifiers = `{
|
||||
"type": "string",
|
||||
"pattern": "^(equals|not)$"
|
||||
}`
|
||||
|
||||
const stringModifiers = `{
|
||||
"type": "string",
|
||||
"pattern": "^(equals|not|contains|starts|ends|in|notin)$"
|
||||
}`
|
||||
|
||||
const filterBool = `{
|
||||
"type": "string",
|
||||
"pattern": "^(TRUE|true|t|yes|y|on|1|FALSE|f|false|n|no|off|0)$"
|
||||
}`
|
||||
|
||||
const filterString = `{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}`
|
||||
|
||||
const baseFilterSchema = `{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
%s
|
||||
]
|
||||
}
|
||||
}`
|
||||
25
backend/internal/entity/host/filters.go
Normal file
25
backend/internal/entity/host/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
171
backend/internal/entity/host/methods.go
Normal file
171
backend/internal/entity/host/methods.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a Host by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a Host from this model
|
||||
func Create(host *Model) (int, error) {
|
||||
if host.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create host when model already has an ID")
|
||||
}
|
||||
|
||||
host.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
user_id,
|
||||
type,
|
||||
listen_interface,
|
||||
domain_names,
|
||||
upstream_id,
|
||||
certificate_id,
|
||||
access_list_id,
|
||||
ssl_forced,
|
||||
caching_enabled,
|
||||
block_exploits,
|
||||
allow_websocket_upgrade,
|
||||
http2_support,
|
||||
hsts_enabled,
|
||||
hsts_subdomains,
|
||||
paths,
|
||||
upstream_options,
|
||||
advanced_config,
|
||||
is_disabled,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:user_id,
|
||||
:type,
|
||||
:listen_interface,
|
||||
:domain_names,
|
||||
:upstream_id,
|
||||
:certificate_id,
|
||||
:access_list_id,
|
||||
:ssl_forced,
|
||||
:caching_enabled,
|
||||
:block_exploits,
|
||||
:allow_websocket_upgrade,
|
||||
:http2_support,
|
||||
:hsts_enabled,
|
||||
:hsts_subdomains,
|
||||
:paths,
|
||||
:upstream_options,
|
||||
:advanced_config,
|
||||
:is_disabled,
|
||||
:is_deleted
|
||||
)`, host)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a Host from this model
|
||||
func Update(host *Model) error {
|
||||
if host.ID == 0 {
|
||||
return goerrors.New("Cannot update host when model doesn't have an ID")
|
||||
}
|
||||
|
||||
host.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
user_id = :user_id,
|
||||
type = :type,
|
||||
listen_interface = :listen_interface,
|
||||
domain_names = :domain_names,
|
||||
upstream_id = :upstream_id,
|
||||
certificate_id = :certificate_id,
|
||||
access_list_id = :access_list_id,
|
||||
ssl_forced = :ssl_forced,
|
||||
caching_enabled = :caching_enabled,
|
||||
block_exploits = :block_exploits,
|
||||
allow_websocket_upgrade = :allow_websocket_upgrade,
|
||||
http2_support = :http2_support,
|
||||
hsts_enabled = :hsts_enabled,
|
||||
hsts_subdomains = :hsts_subdomains,
|
||||
paths = :paths,
|
||||
upstream_options = :upstream_options,
|
||||
advanced_config = :advanced_config,
|
||||
is_disabled = :is_disabled,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, host)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of hosts
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "domain_names",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
94
backend/internal/entity/host/model.go
Normal file
94
backend/internal/entity/host/model.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "host"
|
||||
|
||||
// ProxyHostType ...
|
||||
ProxyHostType = "proxy"
|
||||
// RedirectionHostType ...
|
||||
RedirectionHostType = "redirection"
|
||||
// DeadHostType ...
|
||||
DeadHostType = "dead"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
|
||||
Type string `json:"type" db:"type" filter:"type,string"`
|
||||
ListenInterface string `json:"listen_interface" db:"listen_interface" filter:"listen_interface,string"`
|
||||
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"`
|
||||
UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"`
|
||||
CertificateID int `json:"certificate_id" db:"certificate_id" filter:"certificate_id,integer"`
|
||||
AccessListID int `json:"access_list_id" db:"access_list_id" filter:"access_list_id,integer"`
|
||||
SSLForced bool `json:"ssl_forced" db:"ssl_forced" filter:"ssl_forced,boolean"`
|
||||
CachingEnabled bool `json:"caching_enabled" db:"caching_enabled" filter:"caching_enabled,boolean"`
|
||||
BlockExploits bool `json:"block_exploits" db:"block_exploits" filter:"block_exploits,boolean"`
|
||||
AllowWebsocketUpgrade bool `json:"allow_websocket_upgrade" db:"allow_websocket_upgrade" filter:"allow_websocket_upgrade,boolean"`
|
||||
HTTP2Support bool `json:"http2_support" db:"http2_support" filter:"http2_support,boolean"`
|
||||
HSTSEnabled bool `json:"hsts_enabled" db:"hsts_enabled" filter:"hsts_enabled,boolean"`
|
||||
HSTSSubdomains bool `json:"hsts_subdomains" db:"hsts_subdomains" filter:"hsts_subdomains,boolean"`
|
||||
Paths string `json:"paths" db:"paths" filter:"paths,string"`
|
||||
UpstreamOptions string `json:"upstream_options" db:"upstream_options" filter:"upstream_options,string"`
|
||||
AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"`
|
||||
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, 0}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.UserID == 0 {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a host as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
15
backend/internal/entity/host/structs.go
Normal file
15
backend/internal/entity/host/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for this list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
80
backend/internal/entity/lists_query.go
Normal file
80
backend/internal/entity/lists_query.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListQueryBuilder should be able to return the query and params to get items agnostically based
|
||||
// on given params.
|
||||
func ListQueryBuilder(modelExample interface{}, tableName string, pageInfo *model.PageInfo, defaultSort model.Sort, filters []model.Filter, filterMapFunctions map[string]FilterMapFunction, returnCount bool) (string, []interface{}) {
|
||||
var queryStrings []string
|
||||
var whereStrings []string
|
||||
var params []interface{}
|
||||
|
||||
if returnCount {
|
||||
queryStrings = append(queryStrings, "SELECT COUNT(*)")
|
||||
} else {
|
||||
queryStrings = append(queryStrings, "SELECT *")
|
||||
}
|
||||
|
||||
// nolint: gosec
|
||||
queryStrings = append(queryStrings, fmt.Sprintf("FROM `%s`", tableName))
|
||||
|
||||
// Append filters to where clause:
|
||||
if filters != nil {
|
||||
filterMap := GetFilterMap(modelExample)
|
||||
filterQuery, filterParams := GenerateSQLFromFilters(filters, filterMap, filterMapFunctions)
|
||||
whereStrings = []string{filterQuery}
|
||||
params = append(params, filterParams...)
|
||||
}
|
||||
|
||||
// Add is deletee check if model has the field
|
||||
if hasDeletedField(modelExample) {
|
||||
params = append(params, 0)
|
||||
whereStrings = append(whereStrings, "`is_deleted` = ?")
|
||||
}
|
||||
|
||||
// Append where clauses to query
|
||||
if len(whereStrings) > 0 {
|
||||
// nolint: gosec
|
||||
queryStrings = append(queryStrings, fmt.Sprintf("WHERE %s", strings.Join(whereStrings, " AND ")))
|
||||
}
|
||||
|
||||
if !returnCount {
|
||||
var orderBy string
|
||||
columns := GetDBColumns(modelExample)
|
||||
orderBy, pageInfo.Sort = database.BuildOrderBySQL(columns, &pageInfo.Sort)
|
||||
|
||||
if orderBy != "" {
|
||||
queryStrings = append(queryStrings, orderBy)
|
||||
} else {
|
||||
pageInfo.Sort = append(pageInfo.Sort, defaultSort)
|
||||
queryStrings = append(queryStrings, fmt.Sprintf("ORDER BY `%v` %v", defaultSort.Field, defaultSort.Direction))
|
||||
}
|
||||
|
||||
params = append(params, pageInfo.Offset)
|
||||
params = append(params, pageInfo.Limit)
|
||||
queryStrings = append(queryStrings, "LIMIT ?, ?")
|
||||
}
|
||||
|
||||
return strings.Join(queryStrings, " "), params
|
||||
}
|
||||
|
||||
func hasDeletedField(modelExample interface{}) bool {
|
||||
t := reflect.TypeOf(modelExample)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
dbTag := field.Tag.Get(DBTagName)
|
||||
if dbTag == "is_deleted" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
15
backend/internal/entity/setting/apply.go
Normal file
15
backend/internal/entity/setting/apply.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"npm/internal/config"
|
||||
"npm/internal/logger"
|
||||
)
|
||||
|
||||
// ApplySettings will load settings from the DB and apply them where required
|
||||
func ApplySettings() {
|
||||
logger.Debug("Applying Settings")
|
||||
|
||||
// Error-reporting
|
||||
m, _ := GetByName("error-reporting")
|
||||
config.ErrorReporting = m.Value.Decoded.(bool)
|
||||
}
|
||||
25
backend/internal/entity/setting/filters.go
Normal file
25
backend/internal/entity/setting/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
124
backend/internal/entity/setting/methods.go
Normal file
124
backend/internal/entity/setting/methods.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a setting by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// GetByName finds a setting by name
|
||||
func GetByName(name string) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByName(name)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will Create a Setting from this model
|
||||
func Create(setting *Model) (int, error) {
|
||||
if setting.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create setting when model already has an ID")
|
||||
}
|
||||
|
||||
setting.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
name,
|
||||
value
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:name,
|
||||
:value
|
||||
)`, setting)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a Setting from this model
|
||||
func Update(setting *Model) error {
|
||||
if setting.ID == 0 {
|
||||
return goerrors.New("Cannot update setting when model doesn't have an ID")
|
||||
}
|
||||
|
||||
setting.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
name = :name,
|
||||
value = :value
|
||||
WHERE id = :id`, setting)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of settings
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
69
backend/internal/entity/setting/model.go
Normal file
69
backend/internal/entity/setting/model.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "setting"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
Value types.JSONB `json:"value" db:"value"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE `id` = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// LoadByName will load from a Name
|
||||
func (m *Model) LoadByName(name string) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE LOWER(`name`) = ? LIMIT 1", tableName)
|
||||
params := []interface{}{strings.TrimSpace(strings.ToLower(name))}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
// Reapply settings
|
||||
if err == nil {
|
||||
ApplySettings()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
15
backend/internal/entity/setting/structs.go
Normal file
15
backend/internal/entity/setting/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for users list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
25
backend/internal/entity/stream/filters.go
Normal file
25
backend/internal/entity/stream/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
135
backend/internal/entity/stream/methods.go
Normal file
135
backend/internal/entity/stream/methods.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a auth by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a Auth from this model
|
||||
func Create(host *Model) (int, error) {
|
||||
if host.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create stream when model already has an ID")
|
||||
}
|
||||
|
||||
host.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
user_id,
|
||||
provider,
|
||||
name,
|
||||
domain_names,
|
||||
expires_on,
|
||||
meta,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:user_id,
|
||||
:provider,
|
||||
:name,
|
||||
:domain_names,
|
||||
:expires_on,
|
||||
:meta,
|
||||
:is_deleted
|
||||
)`, host)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a Host from this model
|
||||
func Update(host *Model) error {
|
||||
if host.ID == 0 {
|
||||
return goerrors.New("Cannot update stream when model doesn't have an ID")
|
||||
}
|
||||
|
||||
host.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
user_id = :user_id,
|
||||
provider = :provider,
|
||||
name = :name,
|
||||
domain_names = :domain_names,
|
||||
expires_on = :expires_on,
|
||||
meta = :meta,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, host)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// List will return a list of hosts
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Debug("%s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
75
backend/internal/entity/stream/model.go
Normal file
75
backend/internal/entity/stream/model.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "stream"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
ExpiresOn types.DBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"`
|
||||
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"`
|
||||
Provider string `json:"provider" db:"provider" filter:"provider,string"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"`
|
||||
Meta types.JSONB `json:"-" db:"meta"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
return database.GetByQuery(m, query, params)
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, 0}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
|
||||
if m.UserID == 0 {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a host as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
15
backend/internal/entity/stream/structs.go
Normal file
15
backend/internal/entity/stream/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for this list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
25
backend/internal/entity/user/filters.go
Normal file
25
backend/internal/entity/user/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema ...
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
||||
181
backend/internal/entity/user/methods.go
Normal file
181
backend/internal/entity/user/methods.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a user by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// GetByEmail finds a user by email
|
||||
func GetByEmail(email string) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByEmail(email)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a User from given model
|
||||
func Create(user *Model) (int, error) {
|
||||
// We need to ensure that a user can't be created with the same email
|
||||
// as an existing non-deleted user. Usually you would do this with the
|
||||
// database schema, but it's a bit more complex because of the is_deleted field.
|
||||
|
||||
if user.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create user when model already has an ID")
|
||||
}
|
||||
|
||||
// Check if an existing user with this email exists
|
||||
_, err := GetByEmail(user.Email)
|
||||
if err == nil {
|
||||
return 0, errors.ErrDuplicateEmailUser
|
||||
}
|
||||
|
||||
user.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
name,
|
||||
nickname,
|
||||
email,
|
||||
roles,
|
||||
is_disabled
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:name,
|
||||
:nickname,
|
||||
:email,
|
||||
:roles,
|
||||
:is_disabled
|
||||
)`, user)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a User from this model
|
||||
func Update(user *Model) error {
|
||||
if user.ID == 0 {
|
||||
return goerrors.New("Cannot update user when model doesn't have an ID")
|
||||
}
|
||||
|
||||
// Check that the email address isn't associated with another user
|
||||
if existingUser, _ := GetByEmail(user.Email); existingUser.ID != 0 && existingUser.ID != user.ID {
|
||||
return errors.ErrDuplicateEmailUser
|
||||
}
|
||||
|
||||
user.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
name = :name,
|
||||
nickname = :nickname,
|
||||
email = :email,
|
||||
roles = :roles,
|
||||
is_disabled = :is_disabled,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, user)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// IsEnabled is used by middleware to ensure the user is still enabled
|
||||
// returns (userExist, isEnabled)
|
||||
func IsEnabled(userID int) (bool, bool) {
|
||||
// nolint: gosec
|
||||
query := `SELECT is_disabled FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE id = ? AND is_deleted = ?`
|
||||
disabled := true
|
||||
db := database.GetInstance()
|
||||
err := db.QueryRowx(query, userID, 0).Scan(&disabled)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return false, false
|
||||
} else if err != nil {
|
||||
logger.Error("QueryError", err)
|
||||
}
|
||||
|
||||
return true, !disabled
|
||||
}
|
||||
|
||||
// List will return a list of users
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
logger.Debug("Query: %s -- %+v", query, params)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
logger.Debug("Query: %s -- %+v", query, params)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for idx := range items {
|
||||
items[idx].generateGravatar()
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeleteAll will do just that, and should only be used for testing purposes.
|
||||
func DeleteAll() error {
|
||||
db := database.GetInstance()
|
||||
_, err := db.Exec(fmt.Sprintf("DELETE FROM `%s`", tableName))
|
||||
return err
|
||||
}
|
||||
97
backend/internal/entity/user/model.go
Normal file
97
backend/internal/entity/user/model.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity/auth"
|
||||
"npm/internal/types"
|
||||
|
||||
"github.com/drexedam/gravatar"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "user"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
Nickname string `json:"nickname" db:"nickname" filter:"nickname,string"`
|
||||
Email string `json:"email" db:"email" filter:"email,email"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
Roles types.Roles `json:"roles" db:"roles"`
|
||||
GravatarURL string `json:"gravatar_url"`
|
||||
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
// Expansions
|
||||
Auth *auth.Model `json:"auth,omitempty" db:"-"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
err := database.GetByQuery(m, query, params)
|
||||
m.generateGravatar()
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, false}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// LoadByEmail will load from an Email
|
||||
func (m *Model) LoadByEmail(email string) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE email = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{strings.TrimSpace(strings.ToLower(email)), false}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
m.generateGravatar()
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
// Ensure email is nice
|
||||
m.Email = strings.TrimSpace(strings.ToLower(m.Email))
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a user as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Model) generateGravatar() {
|
||||
m.GravatarURL = gravatar.New(m.Email).
|
||||
Size(128).
|
||||
Default(gravatar.MysteryMan).
|
||||
Rating(gravatar.Pg).
|
||||
AvatarURL()
|
||||
}
|
||||
15
backend/internal/entity/user/structs.go
Normal file
15
backend/internal/entity/user/structs.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for users list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user