internal/schema/siteinfo_schema.go (360 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package schema
import (
"context"
"fmt"
"net/mail"
"net/url"
"path/filepath"
"strings"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/translator"
"github.com/apache/answer/internal/base/validator"
"github.com/segmentfault/pacman/errors"
)
// SiteGeneralReq site general request
type SiteGeneralReq struct {
Name string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"`
ShortDescription string `validate:"omitempty,sanitizer,gt=3,lte=255" form:"short_description" json:"short_description"`
Description string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"`
SiteUrl string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"`
ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"`
}
func (r *SiteGeneralReq) FormatSiteUrl() {
parsedUrl, err := url.Parse(r.SiteUrl)
if err != nil {
return
}
r.SiteUrl = fmt.Sprintf("%s://%s", parsedUrl.Scheme, parsedUrl.Host)
if len(parsedUrl.Path) > 0 {
r.SiteUrl = r.SiteUrl + parsedUrl.Path
r.SiteUrl = strings.TrimSuffix(r.SiteUrl, "/")
}
}
// SiteInterfaceReq site interface request
type SiteInterfaceReq struct {
Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"`
TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"`
}
// SiteBrandingReq site branding request
type SiteBrandingReq struct {
Logo string `validate:"omitempty,gt=0,lte=512" form:"logo" json:"logo"`
MobileLogo string `validate:"omitempty,gt=0,lte=512" form:"mobile_logo" json:"mobile_logo"`
SquareIcon string `validate:"omitempty,gt=0,lte=512" form:"square_icon" json:"square_icon"`
Favicon string `validate:"omitempty,gt=0,lte=512" form:"favicon" json:"favicon"`
}
// SiteWriteReq site write request
type SiteWriteReq struct {
RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"`
RequiredTag bool `validate:"omitempty" json:"required_tag"`
RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"`
ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"`
MaxImageSize int `validate:"omitempty,gt=0" json:"max_image_size"`
MaxAttachmentSize int `validate:"omitempty,gt=0" json:"max_attachment_size"`
MaxImageMegapixel int `validate:"omitempty,gt=0" json:"max_image_megapixel"`
AuthorizedImageExtensions []string `validate:"omitempty" json:"authorized_image_extensions"`
AuthorizedAttachmentExtensions []string `validate:"omitempty" json:"authorized_attachment_extensions"`
UserID string `json:"-"`
}
func (s *SiteWriteResp) GetMaxImageSize() int64 {
if s.MaxImageSize <= 0 {
return constant.DefaultMaxImageSize
}
return int64(s.MaxImageSize) * 1024 * 1024
}
func (s *SiteWriteResp) GetMaxAttachmentSize() int64 {
if s.MaxAttachmentSize <= 0 {
return constant.DefaultMaxAttachmentSize
}
return int64(s.MaxAttachmentSize) * 1024 * 1024
}
func (s *SiteWriteResp) GetMaxImageMegapixel() int {
if s.MaxImageMegapixel <= 0 {
return constant.DefaultMaxImageMegapixel
}
return s.MaxImageMegapixel * 1000 * 1000
}
// SiteWriteTag site write response tag
type SiteWriteTag struct {
SlugName string `validate:"required" json:"slug_name"`
DisplayName string `json:"display_name"`
}
// SiteLegalReq site branding request
type SiteLegalReq struct {
TermsOfServiceOriginalText string `json:"terms_of_service_original_text"`
TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"`
PrivacyPolicyOriginalText string `json:"privacy_policy_original_text"`
PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text"`
ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"`
}
// GetSiteLegalInfoReq site site legal request
type GetSiteLegalInfoReq struct {
InfoType string `validate:"required,oneof=tos privacy" form:"info_type"`
}
func (r *GetSiteLegalInfoReq) IsTOS() bool {
return r.InfoType == "tos"
}
func (r *GetSiteLegalInfoReq) IsPrivacy() bool {
return r.InfoType == "privacy"
}
// GetSiteLegalInfoResp get site legal info response
type GetSiteLegalInfoResp struct {
TermsOfServiceOriginalText string `json:"terms_of_service_original_text,omitempty"`
TermsOfServiceParsedText string `json:"terms_of_service_parsed_text,omitempty"`
PrivacyPolicyOriginalText string `json:"privacy_policy_original_text,omitempty"`
PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text,omitempty"`
}
// SiteUsersReq site users config request
type SiteUsersReq struct {
DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"`
GravatarBaseURL string `json:"gravatar_base_url"`
AllowUpdateDisplayName bool `json:"allow_update_display_name"`
AllowUpdateUsername bool `json:"allow_update_username"`
AllowUpdateAvatar bool `json:"allow_update_avatar"`
AllowUpdateBio bool `json:"allow_update_bio"`
AllowUpdateWebsite bool `json:"allow_update_website"`
AllowUpdateLocation bool `json:"allow_update_location"`
}
// SiteLoginReq site login request
type SiteLoginReq struct {
AllowNewRegistrations bool `json:"allow_new_registrations"`
AllowEmailRegistrations bool `json:"allow_email_registrations"`
AllowPasswordLogin bool `json:"allow_password_login"`
LoginRequired bool `json:"login_required"`
AllowEmailDomains []string `json:"allow_email_domains"`
}
// SiteCustomCssHTMLReq site custom css html
type SiteCustomCssHTMLReq struct {
CustomHead string `validate:"omitempty,gt=0,lte=65536" json:"custom_head"`
CustomCss string `validate:"omitempty,gt=0,lte=65536" json:"custom_css"`
CustomHeader string `validate:"omitempty,gt=0,lte=65536" json:"custom_header"`
CustomFooter string `validate:"omitempty,gt=0,lte=65536" json:"custom_footer"`
CustomSideBar string `validate:"omitempty,gt=0,lte=65536" json:"custom_sidebar"`
}
// SiteThemeReq site theme config
type SiteThemeReq struct {
Theme string `validate:"required,gt=0,lte=255" json:"theme"`
ThemeConfig map[string]interface{} `validate:"omitempty" json:"theme_config"`
ColorScheme string `validate:"omitempty,gt=0,lte=100" json:"color_scheme"`
}
type SiteSeoReq struct {
Permalink int `validate:"required,lte=4,gte=0" form:"permalink" json:"permalink"`
Robots string `validate:"required" form:"robots" json:"robots"`
}
func (s *SiteSeoResp) IsShortLink() bool {
return s.Permalink == constant.PermalinkQuestionIDAndTitleByShortID ||
s.Permalink == constant.PermalinkQuestionIDByShortID
}
// SiteGeneralResp site general response
type SiteGeneralResp SiteGeneralReq
// SiteInterfaceResp site interface response
type SiteInterfaceResp SiteInterfaceReq
// SiteBrandingResp site branding response
type SiteBrandingResp SiteBrandingReq
// SiteLoginResp site login response
type SiteLoginResp SiteLoginReq
// SiteCustomCssHTMLResp site custom css html response
type SiteCustomCssHTMLResp SiteCustomCssHTMLReq
// SiteUsersResp site users response
type SiteUsersResp SiteUsersReq
// SiteThemeResp site theme response
type SiteThemeResp struct {
ThemeOptions []*ThemeOption `json:"theme_options"`
Theme string `json:"theme"`
ThemeConfig map[string]interface{} `json:"theme_config"`
ColorScheme string `json:"color_scheme"`
}
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
la := handler.GetLangByCtx(ctx)
for _, option := range s.ThemeOptions {
tr := translator.Tr(la, option.Value)
// if tr is equal the option value means not found translation, so use the original label
if tr != option.Value {
option.Label = tr
}
}
}
// ThemeOption get label option
type ThemeOption struct {
Label string `json:"label"`
Value string `json:"value"`
}
// SiteWriteResp site write response
type SiteWriteResp SiteWriteReq
// SiteLegalResp site write response
type SiteLegalResp SiteLegalReq
// SiteLegalSimpleResp site write response
type SiteLegalSimpleResp struct {
ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"`
}
// SiteSeoResp site write response
type SiteSeoResp SiteSeoReq
// SiteInfoResp get site info response
type SiteInfoResp struct {
General *SiteGeneralResp `json:"general"`
Interface *SiteInterfaceResp `json:"interface"`
Branding *SiteBrandingResp `json:"branding"`
Login *SiteLoginResp `json:"login"`
Theme *SiteThemeResp `json:"theme"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
SiteSeo *SiteSeoResp `json:"site_seo"`
SiteUsers *SiteUsersResp `json:"site_users"`
Write *SiteWriteResp `json:"site_write"`
Legal *SiteLegalSimpleResp `json:"site_legal"`
Version string `json:"version"`
Revision string `json:"revision"`
}
type TemplateSiteInfoResp struct {
General *SiteGeneralResp `json:"general"`
Interface *SiteInterfaceResp `json:"interface"`
Branding *SiteBrandingResp `json:"branding"`
SiteSeo *SiteSeoResp `json:"site_seo"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
Title string
Year string
Canonical string
JsonLD string
Keywords string
Description string
}
// UpdateSMTPConfigReq get smtp config request
type UpdateSMTPConfigReq struct {
FromEmail string `validate:"omitempty,gt=0,lte=256" json:"from_email"`
FromName string `validate:"omitempty,gt=0,lte=256" json:"from_name"`
SMTPHost string `validate:"omitempty,gt=0,lte=256" json:"smtp_host"`
SMTPPort int `validate:"omitempty,min=1,max=65535" json:"smtp_port"`
Encryption string `validate:"omitempty,oneof=SSL TLS" json:"encryption"` // "" SSL TLS
SMTPUsername string `validate:"omitempty,gt=0,lte=256" json:"smtp_username"`
SMTPPassword string `validate:"omitempty,gt=0,lte=256" json:"smtp_password"`
SMTPAuthentication bool `validate:"omitempty" json:"smtp_authentication"`
TestEmailRecipient string `validate:"omitempty,email" json:"test_email_recipient"`
}
func (r *UpdateSMTPConfigReq) Check() (errField []*validator.FormErrorField, err error) {
_, err = mail.ParseAddress(r.FromName)
if err == nil {
return append(errField, &validator.FormErrorField{
ErrorField: "from_name",
ErrorMsg: reason.SMTPConfigFromNameCannotBeEmail,
}), errors.BadRequest(reason.SMTPConfigFromNameCannotBeEmail)
}
return nil, nil
}
// GetSMTPConfigResp get smtp config response
type GetSMTPConfigResp struct {
FromEmail string `json:"from_email"`
FromName string `json:"from_name"`
SMTPHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"`
Encryption string `json:"encryption"` // "" SSL TLS
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPAuthentication bool `json:"smtp_authentication"`
}
// GetManifestJsonResp get manifest json response
type GetManifestJsonResp struct {
ManifestVersion int `json:"manifest_version"`
Version string `json:"version"`
Revision string `json:"revision"`
ShortName string `json:"short_name"`
Name string `json:"name"`
Icons []ManifestJsonIcon `json:"icons"`
StartUrl string `json:"start_url"`
Display string `json:"display"`
ThemeColor string `json:"theme_color"`
BackgroundColor string `json:"background_color"`
}
type ManifestJsonIcon struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
}
func CreateManifestJsonIcons(icon string) []ManifestJsonIcon {
ext := filepath.Ext(icon)
if ext == "" {
ext = "png"
} else {
ext = strings.ToLower(ext[1:])
}
iconType := fmt.Sprintf("image/%s", ext)
return []ManifestJsonIcon{
{
Src: icon,
Sizes: "16x16",
Type: iconType,
},
{
Src: icon,
Sizes: "32x32",
Type: iconType,
},
{
Src: icon,
Sizes: "48x48",
Type: iconType,
},
{
Src: icon,
Sizes: "128x128",
Type: iconType,
},
}
}
const (
// PrivilegeLevel1 low
PrivilegeLevel1 PrivilegeLevel = 1
// PrivilegeLevel2 medium
PrivilegeLevel2 PrivilegeLevel = 2
// PrivilegeLevel3 high
PrivilegeLevel3 PrivilegeLevel = 3
// PrivilegeLevelCustom custom
PrivilegeLevelCustom PrivilegeLevel = 99
)
type PrivilegeLevel int
type PrivilegeOptions []*PrivilegeOption
func (p PrivilegeOptions) Choose(level PrivilegeLevel) (option *PrivilegeOption) {
for _, op := range p {
if op.Level == level {
return op
}
}
return nil
}
// GetPrivilegesConfigResp get privileges config response
type GetPrivilegesConfigResp struct {
Options []*PrivilegeOption `json:"options"`
SelectedLevel PrivilegeLevel `json:"selected_level"`
}
// PrivilegeOption privilege option
type PrivilegeOption struct {
Level PrivilegeLevel `json:"level"`
LevelDesc string `json:"level_desc"`
Privileges []*constant.Privilege `validate:"dive" json:"privileges"`
}
// UpdatePrivilegesConfigReq update privileges config request
type UpdatePrivilegesConfigReq struct {
Level PrivilegeLevel `validate:"required,min=1,max=3|eq=99" json:"level"`
CustomPrivileges []*constant.Privilege `validate:"dive" json:"custom_privileges"`
}
var (
DefaultPrivilegeOptions PrivilegeOptions
DefaultCustomPrivilegeOption *PrivilegeOption
privilegeOptionsLevelMapping = map[string][]int{
constant.RankQuestionAddKey: {1, 1, 1},
constant.RankAnswerAddKey: {1, 1, 1},
constant.RankCommentAddKey: {1, 1, 1},
constant.RankReportAddKey: {1, 1, 1},
constant.RankCommentVoteUpKey: {1, 1, 1},
constant.RankLinkUrlLimitKey: {1, 10, 10},
constant.RankQuestionVoteUpKey: {1, 8, 15},
constant.RankAnswerVoteUpKey: {1, 8, 15},
constant.RankQuestionVoteDownKey: {125, 125, 125},
constant.RankAnswerVoteDownKey: {125, 125, 125},
constant.RankInviteSomeoneToAnswerKey: {1, 500, 1000},
constant.RankTagAddKey: {1, 750, 1500},
constant.RankTagEditKey: {1, 50, 100},
constant.RankQuestionEditKey: {1, 100, 200},
constant.RankAnswerEditKey: {1, 100, 200},
constant.RankQuestionEditWithoutReviewKey: {1, 1000, 2000},
constant.RankAnswerEditWithoutReviewKey: {1, 1000, 2000},
constant.RankQuestionAuditKey: {1, 1000, 2000},
constant.RankAnswerAuditKey: {1, 1000, 2000},
constant.RankTagAuditKey: {1, 2500, 5000},
constant.RankTagEditWithoutReviewKey: {1, 10000, 20000},
constant.RankTagSynonymKey: {1, 10000, 20000},
}
)
func init() {
DefaultPrivilegeOptions = append(DefaultPrivilegeOptions, &PrivilegeOption{
Level: PrivilegeLevel1,
LevelDesc: reason.PrivilegeLevel1Desc,
}, &PrivilegeOption{
Level: PrivilegeLevel2,
LevelDesc: reason.PrivilegeLevel2Desc,
}, &PrivilegeOption{
Level: PrivilegeLevel3,
LevelDesc: reason.PrivilegeLevel3Desc,
})
for _, option := range DefaultPrivilegeOptions {
for _, privilege := range constant.RankAllPrivileges {
if len(privilegeOptionsLevelMapping[privilege.Key]) == 0 {
continue
}
option.Privileges = append(option.Privileges, &constant.Privilege{
Label: privilege.Label,
Value: privilegeOptionsLevelMapping[privilege.Key][option.Level-1],
Key: privilege.Key,
})
}
}
// set up default custom privilege option
DefaultCustomPrivilegeOption = &PrivilegeOption{
Level: PrivilegeLevelCustom,
LevelDesc: reason.PrivilegeLevelCustomDesc,
Privileges: DefaultPrivilegeOptions[0].Privileges,
}
}