3a5a15fe77
Replace OptionalBool with an explicit require_email_verification value for the login settings save request while keeping legacy read defaults intact. Use positive RequireEmailVerification naming through the registration flow and inline the site setting mapping. Add validation, save-path, and registration coverage for the explicit email verification setting.
597 lines
22 KiB
Go
597 lines
22 KiB
Go
/*
|
|
* 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"`
|
|
}
|
|
|
|
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 += 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"`
|
|
// Deperecated: use SiteUsersSettingsReq instead
|
|
DefaultAvatar string `validate:"omitempty" json:"-"`
|
|
// Deperecated: use SiteUsersSettingsReq instead
|
|
GravatarBaseURL string `validate:"omitempty" json:"-"`
|
|
}
|
|
|
|
// SiteInterfaceSettingsReq site interface settings request
|
|
type SiteInterfaceSettingsReq struct {
|
|
Language string `validate:"required,gt=1,lte=128" json:"language"`
|
|
TimeZone string `validate:"required,gt=1,lte=128" json:"time_zone"`
|
|
}
|
|
|
|
type SiteInterfaceSettingsResp SiteInterfaceSettingsReq
|
|
|
|
type SiteUsersSettingsReq struct {
|
|
DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"`
|
|
GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"`
|
|
}
|
|
|
|
type SiteUsersSettingsResp SiteUsersSettingsReq
|
|
|
|
// 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 use SiteQuestionsReq, SiteAdvancedReq and SiteTagsReq instead
|
|
type SiteWriteReq struct {
|
|
MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"`
|
|
RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"`
|
|
MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"`
|
|
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:"-"`
|
|
}
|
|
|
|
type SiteWriteResp SiteWriteReq
|
|
|
|
// SiteQuestionsReq site questions settings request
|
|
type SiteQuestionsReq struct {
|
|
MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"`
|
|
MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"`
|
|
RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"`
|
|
}
|
|
|
|
// SiteAdvancedReq site advanced settings request
|
|
type SiteAdvancedReq struct {
|
|
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"`
|
|
}
|
|
|
|
// SiteTagsReq site tags settings request
|
|
type SiteTagsReq struct {
|
|
ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"`
|
|
RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"`
|
|
RequiredTag bool `validate:"omitempty" json:"required_tag"`
|
|
UserID string `json:"-"`
|
|
}
|
|
|
|
func (s *SiteAdvancedResp) GetMaxImageSize() int64 {
|
|
if s.MaxImageSize <= 0 {
|
|
return constant.DefaultMaxImageSize
|
|
}
|
|
return int64(s.MaxImageSize) * 1024 * 1024
|
|
}
|
|
|
|
func (s *SiteAdvancedResp) GetMaxAttachmentSize() int64 {
|
|
if s.MaxAttachmentSize <= 0 {
|
|
return constant.DefaultMaxAttachmentSize
|
|
}
|
|
return int64(s.MaxAttachmentSize) * 1024 * 1024
|
|
}
|
|
|
|
func (s *SiteAdvancedResp) 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 use SitePoliciesReq and SiteSecurityReq instead
|
|
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"`
|
|
}
|
|
|
|
type SitePoliciesReq 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"`
|
|
}
|
|
|
|
type SiteSecurityReq struct {
|
|
LoginRequired bool `json:"login_required"`
|
|
ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"`
|
|
CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"`
|
|
}
|
|
|
|
type SitePoliciesResp SitePoliciesReq
|
|
type SiteSecurityResp SiteSecurityReq
|
|
|
|
// 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"`
|
|
AllowEmailDomains []string `json:"allow_email_domains"`
|
|
RequireEmailVerification *bool `validate:"required" json:"require_email_verification" swaggertype:"boolean"`
|
|
}
|
|
|
|
// SiteLoginResp site login response
|
|
type SiteLoginResp struct {
|
|
AllowNewRegistrations bool `json:"allow_new_registrations"`
|
|
AllowEmailRegistrations bool `json:"allow_email_registrations"`
|
|
AllowPasswordLogin bool `json:"allow_password_login"`
|
|
AllowEmailDomains []string `json:"allow_email_domains"`
|
|
RequireEmailVerification bool `json:"require_email_verification"`
|
|
}
|
|
|
|
// 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]any `validate:"omitempty" json:"theme_config"`
|
|
ColorScheme string `validate:"omitempty,gt=0,lte=100" json:"color_scheme"`
|
|
Layout string `validate:"omitempty,oneof=Full-width Fixed-width" json:"layout"`
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// AIPromptConfig AI prompt configuration for different languages
|
|
type AIPromptConfig struct {
|
|
ZhCN string `json:"zh_cn"`
|
|
EnUS string `json:"en_us"`
|
|
}
|
|
|
|
// SiteAIReq AI configuration request
|
|
type SiteAIReq struct {
|
|
Enabled bool `validate:"omitempty" form:"enabled" json:"enabled"`
|
|
ChosenProvider string `validate:"omitempty,lte=50" form:"chosen_provider" json:"chosen_provider"`
|
|
SiteAIProviders []*SiteAIProvider `validate:"omitempty,dive" form:"ai_providers" json:"ai_providers"`
|
|
PromptConfig *AIPromptConfig `validate:"omitempty" form:"prompt_config" json:"prompt_config,omitempty"`
|
|
}
|
|
|
|
func (s *SiteAIResp) GetProvider() *SiteAIProvider {
|
|
if !s.Enabled || s.ChosenProvider == "" {
|
|
return &SiteAIProvider{}
|
|
}
|
|
if len(s.SiteAIProviders) == 0 {
|
|
return &SiteAIProvider{}
|
|
}
|
|
for _, provider := range s.SiteAIProviders {
|
|
if provider.Provider == s.ChosenProvider {
|
|
return provider
|
|
}
|
|
}
|
|
return &SiteAIProvider{}
|
|
}
|
|
|
|
type SiteAIProvider struct {
|
|
Provider string `validate:"omitempty,lte=50" form:"provider" json:"provider"`
|
|
APIHost string `validate:"omitempty,lte=512" form:"api_host" json:"api_host"`
|
|
APIKey string `validate:"omitempty,lte=256" form:"api_key" json:"api_key"`
|
|
Model string `validate:"omitempty,lte=100" form:"model" json:"model"`
|
|
}
|
|
|
|
// SiteAIResp AI configuration response
|
|
type SiteAIResp SiteAIReq
|
|
|
|
type SiteMCPReq struct {
|
|
Enabled bool `validate:"omitempty" form:"enabled" json:"enabled"`
|
|
}
|
|
|
|
type SiteMCPResp struct {
|
|
Enabled bool `json:"enabled"`
|
|
Type string `json:"type"`
|
|
URL string `json:"url"`
|
|
HTTPHeader string `json:"http_header"`
|
|
}
|
|
|
|
// SiteGeneralResp site general response
|
|
type SiteGeneralResp SiteGeneralReq
|
|
|
|
// SiteInterfaceResp site interface response
|
|
type SiteInterfaceResp SiteInterfaceReq
|
|
|
|
// SiteBrandingResp site branding response
|
|
type SiteBrandingResp SiteBrandingReq
|
|
|
|
// 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]any `json:"theme_config"`
|
|
ColorScheme string `json:"color_scheme"`
|
|
Layout string `json:"layout"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
type SiteQuestionsResp SiteQuestionsReq
|
|
type SiteAdvancedResp SiteAdvancedReq
|
|
type SiteTagsResp SiteTagsReq
|
|
|
|
// SiteLegalResp site write response use SitePoliciesResp and SiteSecurityResp instead
|
|
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 *SiteInterfaceSettingsResp `json:"interface"`
|
|
UsersSettings *SiteUsersSettingsResp `json:"users_settings"`
|
|
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"`
|
|
Advanced *SiteAdvancedResp `json:"site_advanced"`
|
|
Questions *SiteQuestionsResp `json:"site_questions"`
|
|
Tags *SiteTagsResp `json:"site_tags"`
|
|
Legal *SiteLegalSimpleResp `json:"site_legal"`
|
|
Security *SiteSecurityResp `json:"site_security"`
|
|
Version string `json:"version"`
|
|
Revision string `json:"revision"`
|
|
AIEnabled bool `json:"ai_enabled"`
|
|
MCPEnabled bool `json:"mcp_enabled"`
|
|
}
|
|
|
|
type TemplateSiteInfoResp struct {
|
|
General *SiteGeneralResp `json:"general"`
|
|
Interface *SiteInterfaceSettingsResp `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,
|
|
}
|
|
}
|