chore: import upstream snapshot with attribution
CI / Check and lint (push) Failing after 1s
Lint / Lint (ubuntu-latest) (push) Failing after 1s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:22:32 +08:00
commit 39dbe3a57d
1131 changed files with 272709 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
/*
* 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 "github.com/apache/answer/internal/base/constant"
// ActivityMsg activity message
type ActivityMsg struct {
UserID string
TriggerUserID int64
ObjectID string
OriginalObjectID string
ActivityTypeKey constant.ActivityTypeKey
RevisionID string
ExtraInfo map[string]string
}
// GetObjectTimelineReq get object timeline request
type GetObjectTimelineReq struct {
ObjectID string `validate:"omitempty,gt=0,lte=100" form:"object_id"`
ShowVote bool `validate:"omitempty" form:"show_vote"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
IsAdminModerator bool `json:"-"`
}
// GetObjectTimelineResp get object timeline response
type GetObjectTimelineResp struct {
ObjectInfo *ActObjectInfo `json:"object_info"`
Timeline []*ActObjectTimeline `json:"timeline"`
}
// ActObjectTimeline act object timeline
type ActObjectTimeline struct {
ActivityID string `json:"activity_id"`
RevisionID string `json:"revision_id"`
CreatedAt int64 `json:"created_at"`
ActivityType string `json:"activity_type"`
Comment string `json:"comment"`
ObjectID string `json:"object_id"`
ObjectType string `json:"object_type"`
Cancelled bool `json:"cancelled"`
CancelledAt int64 `json:"cancelled_at"`
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
}
// ActObjectInfo act object info
type ActObjectInfo struct {
Title string `json:"title"`
ObjectType string `json:"object_type"`
QuestionID string `json:"question_id"`
AnswerID string `json:"answer_id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
MainTagSlugName string `json:"main_tag_slug_name"`
}
// GetObjectTimelineDetailReq get object timeline detail request
type GetObjectTimelineDetailReq struct {
NewRevisionID string `validate:"required,gt=0,lte=100" form:"new_revision_id"`
OldRevisionID string `validate:"required,gt=0,lte=100" form:"old_revision_id"`
UserID string `json:"-"`
IsAdminModerator bool `json:"-"`
}
// GetObjectTimelineDetailResp get object timeline detail response
type GetObjectTimelineDetailResp struct {
NewRevision *ObjectTimelineDetail `json:"new_revision"`
OldRevision *ObjectTimelineDetail `json:"old_revision"`
}
// ObjectTimelineDetail object timeline detail
type ObjectTimelineDetail struct {
Title string `json:"title"`
Tags []*ObjectTimelineTag `json:"tags"`
OriginalText string `json:"original_text"`
SlugName string `json:"slug_name"`
MainTagSlugName string `json:"main_tag_slug_name"`
}
// ObjectTimelineTag object timeline tags
type ObjectTimelineTag struct {
SlugName string `json:"slug_name"`
DisplayName string `json:"display_name"`
MainTagSlugName string `json:"main_tag_slug_name"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
// PassReviewActivity pass review activity
type PassReviewActivity struct {
UserID string `json:"user_id"`
TriggerUserID string `json:"trigger_user_id"`
ObjectID string `json:"object_id"`
OriginalObjectID string `json:"original_object_id"`
RevisionID string `json:"revision_id"`
}
+51
View File
@@ -0,0 +1,51 @@
/*
* 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
// GetAIProviderResp get AI providers response
type GetAIProviderResp struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
DefaultAPIHost string `json:"default_api_host"`
}
// GetAIModelsResp get AI model response
type GetAIModelsResp struct {
Object string `json:"object"`
Data []struct {
Id string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
OwnedBy string `json:"owned_by"`
} `json:"data"`
}
type GetAIModelsReq struct {
APIHost string `json:"api_host"`
APIKey string `json:"api_key"`
}
// GetAIModelResp get AI model response
type GetAIModelResp struct {
Id string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
OwnedBy string `json:"owned_by"`
}
+124
View File
@@ -0,0 +1,124 @@
/*
* 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 (
"github.com/apache/answer/internal/base/validator"
)
// AIConversationListReq ai conversation list req
type AIConversationListReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
UserID string `validate:"omitempty" json:"-"`
}
// AIConversationListItem ai conversation list item
type AIConversationListItem struct {
ConversationID string `json:"conversation_id"`
Topic string `json:"topic"`
CreatedAt int64 `json:"created_at"`
}
// AIConversationDetailReq ai conversation detail req
type AIConversationDetailReq struct {
ConversationID string `validate:"required" form:"conversation_id" json:"conversation_id"`
UserID string `validate:"omitempty" json:"-"`
}
// AIConversationRecord ai conversation record
type AIConversationRecord struct {
ChatCompletionID string `json:"chat_completion_id"`
Role string `json:"role"`
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"`
Helpful int `json:"helpful"`
Unhelpful int `json:"unhelpful"`
CreatedAt int64 `json:"created_at"`
}
// AIConversationDetailResp ai conversation detail resp
type AIConversationDetailResp struct {
ConversationID string `json:"conversation_id"`
Topic string `json:"topic"`
Records []*AIConversationRecord `json:"records"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
// AIConversationVoteReq ai conversation vote req
type AIConversationVoteReq struct {
ChatCompletionID string `validate:"required" json:"chat_completion_id"`
VoteType string `validate:"required,oneof=helpful unhelpful" json:"vote_type"`
Cancel bool `validate:"omitempty" json:"cancel"`
UserID string `validate:"omitempty" json:"-"`
}
// AIConversationAdminListReq ai conversation admin list req
type AIConversationAdminListReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
}
// AIConversationAdminListItem ai conversation admin list item
type AIConversationAdminListItem struct {
ID string `json:"id"`
Topic string `json:"topic"`
UserInfo AIConversationUserInfo `json:"user_info"`
HelpfulCount int64 `json:"helpful_count"`
UnhelpfulCount int64 `json:"unhelpful_count"`
CreatedAt int64 `json:"created_at"`
}
// AIConversationUserInfo ai conversation user info
type AIConversationUserInfo struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
Avatar string `json:"avatar"`
Rank int `json:"rank"`
}
// AIConversationAdminDetailReq ai conversation admin detail req
type AIConversationAdminDetailReq struct {
ConversationID string `validate:"required" form:"conversation_id" json:"conversation_id"`
}
// AIConversationAdminDetailResp ai conversation admin detail resp
type AIConversationAdminDetailResp struct {
ConversationID string `json:"conversation_id"`
Topic string `json:"topic"`
UserInfo AIConversationUserInfo `json:"user_info"`
Records []AIConversationRecord `json:"records"`
CreatedAt int64 `json:"created_at"`
}
// AIConversationAdminDeleteReq admin delete ai
type AIConversationAdminDeleteReq struct {
ConversationID string `validate:"required" json:"conversation_id"`
}
func (req *AIConversationDetailReq) Check() (errFields []*validator.FormErrorField, err error) {
return nil, nil
}
func (req *AIConversationVoteReq) Check() (errFields []*validator.FormErrorField, err error) {
return nil, nil
}
+55
View File
@@ -0,0 +1,55 @@
/*
* 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
// AcceptAnswerOperationInfo accept answer operation info
type AcceptAnswerOperationInfo struct {
TriggerUserID string
QuestionObjectID string
QuestionUserID string
AnswerObjectID string
AnswerUserID string
// vote activity info
Activities []*AcceptAnswerActivity
}
// AcceptAnswerActivity accept answer activity
type AcceptAnswerActivity struct {
ActivityType int
ActivityUserID string
TriggerUserID string
OriginalObjectID string
Rank int
}
func (v *AcceptAnswerActivity) HasRank() int {
if v.Rank != 0 {
return 1
}
return 0
}
func (a *AcceptAnswerOperationInfo) GetUserIDs() (userIDs []string) {
for _, act := range a.Activities {
userIDs = append(userIDs, act.ActivityUserID)
}
return userIDs
}
+176
View File
@@ -0,0 +1,176 @@
/*
* 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 (
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/pkg/converter"
"github.com/segmentfault/pacman/errors"
)
// RemoveAnswerReq delete answer request
type RemoveAnswerReq struct {
ID string `validate:"required" json:"id"`
UserID string `json:"-"`
CanDelete bool `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
// RecoverAnswerReq recover answer request
type RecoverAnswerReq struct {
AnswerID string `validate:"required" json:"answer_id"`
UserID string `json:"-"`
}
const (
AnswerAcceptedFailed = 1
AnswerAcceptedEnable = 2
)
type AnswerAddReq struct {
QuestionID string `json:"question_id"`
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
HTML string `json:"-"`
UserID string `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanRecover bool `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
IP string `json:"-"`
UserAgent string `json:"-"`
}
func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err error) {
req.HTML = converter.Markdown2HTML(req.Content)
if req.HTML == "" {
return append(errFields, &validator.FormErrorField{
ErrorField: "content",
ErrorMsg: reason.AnswerContentCannotEmpty,
}), errors.BadRequest(reason.AnswerContentCannotEmpty)
}
return nil, nil
}
type GetAnswerInfoResp struct {
Info *AnswerInfo `json:"info"`
Question *QuestionInfoResp `json:"question"`
}
type AnswerUpdateReq struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
EditSummary string `validate:"omitempty" json:"edit_summary"`
HTML string `json:"-"`
UserID string `json:"-"`
NoNeedReview bool `json:"-"`
CanEdit bool `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
func (req *AnswerUpdateReq) Check() (errFields []*validator.FormErrorField, err error) {
req.HTML = converter.Markdown2HTML(req.Content)
if req.HTML == "" {
return append(errFields, &validator.FormErrorField{
ErrorField: "content",
ErrorMsg: reason.AnswerContentCannotEmpty,
}), errors.BadRequest(reason.AnswerContentCannotEmpty)
}
return nil, nil
}
// AnswerUpdateResp answer update resp
type AnswerUpdateResp struct {
WaitForReview bool `json:"wait_for_review"`
}
type AnswerListReq struct {
QuestionID string `json:"question_id" form:"question_id"`
Order string `json:"order" form:"order"`
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
IsAdminModerator bool `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanRecover bool `json:"-"`
}
type AnswerInfo struct {
ID string `json:"id"`
QuestionID string `json:"question_id"`
Content string `json:"content"`
HTML string `json:"html"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
Accepted int `json:"accepted"`
UserID string `json:"-"`
UpdateUserID string `json:"-"`
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"`
Collected bool `json:"collected"`
VoteStatus string `json:"vote_status"`
VoteCount int `json:"vote_count"`
QuestionInfo *QuestionInfoResp `json:"question_info,omitempty"`
Status int `json:"status"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`
}
type AdminAnswerInfo struct {
ID string `json:"id"`
QuestionID string `json:"question_id"`
Description string `json:"description"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
Accepted int `json:"accepted"`
UserID string `json:"-"`
UpdateUserID string `json:"-"`
UserInfo *UserBasicInfo `json:"user_info"`
VoteCount int `json:"vote_count"`
QuestionInfo struct {
Title string `json:"title"`
} `json:"question_info"`
}
type AcceptAnswerReq struct {
QuestionID string `validate:"required,gt=0,lte=30" json:"question_id"`
AnswerID string `validate:"omitempty" json:"answer_id"`
UserID string `json:"-"`
}
func (req *AcceptAnswerReq) Check() (errFields []*validator.FormErrorField, err error) {
if len(req.AnswerID) == 0 {
req.AnswerID = "0"
}
return nil, nil
}
type AdminUpdateAnswerStatusReq struct {
AnswerID string `validate:"required" json:"answer_id"`
Status string `validate:"required,oneof=available deleted" json:"status"`
UserID string `json:"-"`
}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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
// GetAPIKeyReq get api key request
type GetAPIKeyReq struct {
UserID string `json:"-"`
}
// GetAPIKeyResp get api keys response
type GetAPIKeyResp struct {
ID int `json:"id"`
AccessKey string `json:"access_key"`
Description string `json:"description"`
Scope string `json:"scope"`
CreatedAt int64 `json:"created_at"`
LastUsedAt int64 `json:"last_used_at"`
}
// AddAPIKeyReq add api key request
type AddAPIKeyReq struct {
Description string `validate:"required,notblank,lte=150" json:"description"`
Scope string `validate:"required,oneof=read-only global" json:"scope"`
UserID string `json:"-"`
}
// AddAPIKeyResp add api key response
type AddAPIKeyResp struct {
AccessKey string `json:"access_key"`
}
// UpdateAPIKeyReq update api key request
type UpdateAPIKeyReq struct {
ID int `validate:"required" json:"id"`
Description string `validate:"required,notblank,lte=150" json:"description"`
UserID string `json:"-"`
}
// DeleteAPIKeyReq delete api key request
type DeleteAPIKeyReq struct {
ID int `json:"id"`
UserID string `json:"-"`
}
+258
View File
@@ -0,0 +1,258 @@
/*
* 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"
"strings"
"time"
"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/apache/answer/internal/entity"
"github.com/segmentfault/pacman/errors"
)
// UpdateUserStatusReq update user request
type UpdateUserStatusReq struct {
UserID string `validate:"required" json:"user_id"`
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
SuspendDuration string `validate:"omitempty,oneof=24h 48h 72h 7d 14d 1m 2m 3m 6m 1y forever" json:"suspend_duration"`
RemoveAllContent bool `validate:"omitempty" json:"remove_all_content"`
LoginUserID string `json:"-"`
}
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == constant.UserNormal }
func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == constant.UserSuspended }
func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == constant.UserDeleted }
func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == constant.UserInactive }
// GetSuspendedUntil calculates the suspended until time based on duration
func (r *UpdateUserStatusReq) GetSuspendedUntil() time.Time {
if !r.IsSuspended() || r.SuspendDuration == "" || r.SuspendDuration == "forever" {
return entity.PermanentSuspensionTime // permanent suspension
}
now := time.Now()
switch r.SuspendDuration {
case "24h":
return now.Add(24 * time.Hour)
case "48h":
return now.Add(48 * time.Hour)
case "72h":
return now.Add(72 * time.Hour)
case "7d":
return now.Add(7 * 24 * time.Hour)
case "14d":
return now.Add(14 * 24 * time.Hour)
case "1m":
return now.AddDate(0, 1, 0)
case "2m":
return now.AddDate(0, 2, 0)
case "3m":
return now.AddDate(0, 3, 0)
case "6m":
return now.AddDate(0, 6, 0)
case "1y":
return now.AddDate(1, 0, 0)
default:
return entity.PermanentSuspensionTime // fallback to permanent
}
}
// GetUserPageReq get user list page request
type GetUserPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// email
Query string `validate:"omitempty,gt=0,lte=100" form:"query"`
// user status
Status string `validate:"omitempty,oneof=normal suspended deleted inactive" form:"status"`
// staff, if staff is true means query admin or moderator
Staff bool `validate:"omitempty" form:"staff"`
}
func (r *GetUserPageReq) IsSuspended() bool { return r.Status == constant.UserSuspended }
func (r *GetUserPageReq) IsDeleted() bool { return r.Status == constant.UserDeleted }
func (r *GetUserPageReq) IsInactive() bool { return r.Status == constant.UserInactive }
// GetUserPageResp get user response
type GetUserPageResp struct {
// user id
UserID string `json:"user_id"`
// create time
CreatedAt int64 `json:"created_at"`
// delete time
DeletedAt int64 `json:"deleted_at"`
// suspended time
SuspendedAt int64 `json:"suspended_at"`
// suspended until time
SuspendedUntil int64 `json:"suspended_until"`
// username
Username string `json:"username"`
// email
EMail string `json:"e_mail"`
// rank
Rank int `json:"rank"`
// user status(normal,suspended,deleted,inactive)
Status string `json:"status"`
// display name
DisplayName string `json:"display_name"`
// avatar
Avatar string `json:"avatar"`
// role id
RoleID int `json:"role_id"`
// role name
RoleName string `json:"role_name"`
}
// GetUserInfoReq get user request
type GetUserInfoReq struct {
UserID string `validate:"required" json:"user_id"`
}
// GetUserInfoResp get user response
type GetUserInfoResp struct {
// suspended until
SuspendedUntil time.Time `json:"suspended_until"`
}
// UpdateUserRoleReq update user role request
type UpdateUserRoleReq struct {
// user id
UserID string `validate:"required" json:"user_id"`
// role id
RoleID int `validate:"required" json:"role_id"`
// login user id
LoginUserID string `json:"-"`
}
// EditUserProfileReq edit user profile request
type EditUserProfileReq struct {
UserID string `validate:"required" json:"user_id"`
DisplayName string `validate:"required,gte=2,lte=30" json:"display_name"`
Username string `validate:"omitempty,gte=2,lte=30" json:"username"`
Email string `validate:"required,email,gt=0,lte=500" json:"email"`
LoginUserID string `json:"-"`
IsAdmin bool `json:"-"`
}
// AddUserReq add user request
type AddUserReq struct {
DisplayName string `validate:"required,gte=2,lte=30" json:"display_name"`
Email string `validate:"required,email,gt=0,lte=500" json:"email"`
Password string `validate:"required,gte=8,lte=32" json:"password"`
LoginUserID string `json:"-"`
}
// AddUsersReq add users request
type AddUsersReq struct {
// users info line by line
UsersStr string `json:"users"`
Users []*AddUserReq `json:"-"`
}
// DeletePermanentlyReq delete permanently request
type DeletePermanentlyReq struct {
Type string `validate:"required,oneof=users questions answers" json:"type"`
}
type AddUsersErrorData struct {
// optional. error field name.
Field string `json:"field"`
// must. error line number.
Line int `json:"line"`
// must. error content.
Content string `json:"content"`
// optional. error message.
ExtraMessage string `json:"extra_message"`
}
func (e *AddUsersErrorData) GetErrField(ctx context.Context) (errFields []*validator.FormErrorField) {
return append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "users",
ErrorMsg: translator.TrWithData(handler.GetLangByCtx(ctx), reason.AddBulkUsersFormatError, e),
})
}
func (req *AddUsersReq) ParseUsers(ctx context.Context) (errFields []*validator.FormErrorField, err error) {
req.UsersStr = strings.TrimSpace(req.UsersStr)
lines := strings.Split(req.UsersStr, "\n")
req.Users = make([]*AddUserReq, 0)
for i, line := range lines {
arr := strings.Split(line, ",")
if len(arr) != 3 {
errFields = append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "users",
ErrorMsg: translator.TrWithData(handler.GetLangByCtx(ctx), reason.AddBulkUsersFormatError,
&AddUsersErrorData{
Line: i + 1,
Content: line,
}),
})
return errFields, errors.BadRequest(reason.RequestFormatError)
}
req.Users = append(req.Users, &AddUserReq{
DisplayName: strings.TrimSpace(arr[0]),
Email: strings.TrimSpace(arr[1]),
Password: strings.TrimSpace(arr[2]),
})
}
// check users amount
if len(req.Users) == 0 || len(req.Users) > constant.DefaultBulkUser {
errFields = append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "users",
ErrorMsg: translator.TrWithData(handler.GetLangByCtx(ctx), reason.AddBulkUsersAmountError,
map[string]int{
"MaxAmount": constant.DefaultBulkUser,
}),
})
return errFields, errors.BadRequest(reason.RequestFormatError)
}
return nil, nil
}
// UpdateUserPasswordReq update user password request
type UpdateUserPasswordReq struct {
UserID string `validate:"required" json:"user_id"`
Password string `validate:"required,gte=8,lte=32" json:"password"`
LoginUserID string `json:"-"`
}
// GetUserActivationReq get user activation
type GetUserActivationReq struct {
UserID string `validate:"required" form:"user_id"`
}
// GetUserActivationResp get user activation
type GetUserActivationResp struct {
ActivationURL string `json:"activation_url"`
}
// SendUserActivationReq send user activation
type SendUserActivationReq struct {
UserID string `validate:"required" json:"user_id"`
}
+177
View File
@@ -0,0 +1,177 @@
/*
* 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 "github.com/apache/answer/internal/entity"
const (
BadgeStatusActive BadgeStatus = "active"
BadgeStatusInactive BadgeStatus = "inactive"
)
type BadgeStatus string
var BadgeStatusMap = map[int8]BadgeStatus{
entity.BadgeStatusActive: BadgeStatusActive,
entity.BadgeStatusInactive: BadgeStatusInactive,
}
var BadgeStatusEMap = map[BadgeStatus]int8{
BadgeStatusActive: entity.BadgeStatusActive,
BadgeStatusInactive: entity.BadgeStatusInactive,
}
// BadgeListInfo get badge list response
type BadgeListInfo struct {
// badge id
ID string `json:"id" `
// badge name
Name string `json:"name" `
// badge icon
Icon string `json:"icon" `
// badge award count
AwardCount int `json:"award_count" `
// badge earned count
EarnedCount int64 `json:"earned_count" `
// badge level
Level entity.BadgeLevel `json:"level" `
}
type GetBadgeListResp struct {
// badge list info
Badges []*BadgeListInfo `json:"badges" `
// badge group name
GroupName string `json:"group_name" `
}
type UpdateBadgeStatusReq struct {
// badge id
ID string `validate:"required" json:"id"`
// badge status
Status BadgeStatus `validate:"required" json:"status"`
}
type GetBadgeListPagedReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// badge status
Status BadgeStatus `validate:"omitempty" form:"status"`
// query condition
Query string `validate:"omitempty" form:"q"`
}
type GetBadgeListPagedResp struct {
// badge id
ID string `json:"id" `
// badge name
Name string `json:"name" `
// badge description
Description string `json:"description" `
// badge icon
Icon string `json:"icon" `
// badge award count
AwardCount int `json:"award_count" `
// badge earned count
Earned bool `json:"earned" `
// badge level
Level entity.BadgeLevel `json:"level" `
// badge group name
GroupName string `json:"group_name" `
// badge status
Status BadgeStatus `json:"status"`
}
type GetBadgeInfoResp struct {
// badge id
ID string `json:"id" `
// badge name
Name string `json:"name" `
// badge description
Description string `json:"description" `
// badge icon
Icon string `json:"icon" `
// badge award count
AwardCount int `json:"award_count" `
// badge earned count
EarnedCount int64 `json:"earned_count" `
// badge is single or multiple
IsSingle bool `json:"is_single" `
// badge level
Level entity.BadgeLevel `json:"level" `
}
type GetBadgeAwardWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// badge id
BadgeID string `validate:"required" form:"badge_id"`
// username
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
// user id
UserID string `json:"-"`
}
type GetBadgeAwardWithPageResp struct {
// created time
CreatedAt int64 `json:"created_at"`
// object id
ObjectID string `json:"object_id"`
// question id
QuestionID string `json:"question_id"`
// answer id
AnswerID string `json:"answer_id"`
// comment id
CommentID string `json:"comment_id"`
// object type
ObjectType string `json:"object_type" enums:"question,answer,comment"`
// url title
UrlTitle string `json:"url_title"`
// author user info
AuthorUserInfo UserBasicInfo `json:"author_user_info"`
}
type GetUserBadgeAwardListReq struct {
// username
Username string `validate:"required,gt=0,lte=100" form:"username"`
// user id
UserID string `json:"-"`
Limit int `json:"-"`
}
type GetUserBadgeAwardListResp struct {
// badge id
ID string `json:"id" `
// badge name
Name string `json:"name" `
// badge icon
Icon string `json:"icon" `
// badge award count
EarnedCount int64 `json:"earned_count" `
// badge level
Level entity.BadgeLevel `json:"level" `
}
type BadgeTplData struct {
ProfileURL string
}
@@ -0,0 +1,86 @@
/*
* 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 "time"
const (
CGDefault = 1
CGDIY = 2
)
// CollectionSwitchReq switch collection request
type CollectionSwitchReq struct {
ObjectID string `validate:"required" json:"object_id"`
GroupID string `validate:"required" json:"group_id"`
Bookmark bool `validate:"omitempty" json:"bookmark"`
UserID string `json:"-"`
}
// CollectionSwitchResp switch collection response
type CollectionSwitchResp struct {
ObjectCollectionCount int64 `json:"object_collection_count"`
}
// AddCollectionGroupReq add collection group request
type AddCollectionGroupReq struct {
//
UserID int64 `validate:"required" comment:"" json:"user_id"`
// the collection group name
Name string `validate:"required,gt=0,lte=50" comment:"the collection group name" json:"name"`
// mark this group is default, default 1
DefaultGroup int `validate:"required" comment:"mark this group is default, default 1" json:"default_group"`
//
CreateTime time.Time `validate:"required" comment:"" json:"create_time"`
//
UpdateTime time.Time `validate:"required" comment:"" json:"update_time"`
}
// UpdateCollectionGroupReq update collection group request
type UpdateCollectionGroupReq struct {
//
ID int64 `validate:"required" comment:"" json:"id"`
//
UserID int64 `validate:"omitempty" comment:"" json:"user_id"`
// the collection group name
Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" json:"name"`
// mark this group is default, default 1
DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" json:"default_group"`
//
CreateTime time.Time `validate:"omitempty" comment:"" json:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"`
}
// GetCollectionGroupResp get collection group response
type GetCollectionGroupResp struct {
//
ID int64 `json:"id"`
//
UserID int64 `json:"user_id"`
// the collection group name
Name string `json:"name"`
// mark this group is default, default 1
DefaultGroup int `json:"default_group"`
//
CreateTime time.Time `json:"create_time"`
//
UpdateTime time.Time `json:"update_time"`
}
+258
View File
@@ -0,0 +1,258 @@
/*
* 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 (
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/pkg/converter"
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
)
// AddCommentReq add comment request
type AddCommentReq struct {
// object id
ObjectID string `validate:"required" json:"object_id"`
// reply comment id
ReplyCommentID string `validate:"omitempty" json:"reply_comment_id"`
// original comment content
OriginalText string `validate:"required,notblank,gte=2,lte=600" json:"original_text"`
// parsed comment content
ParsedText string `json:"-"`
// @ user id list
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
// user id
UserID string `json:"-"`
// whether user can add it
CanAdd bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
IP string `json:"-"`
UserAgent string `json:"-"`
}
func (req *AddCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
req.ParsedText = converter.Markdown2HTML(req.OriginalText)
if req.ParsedText == "" {
return append(errFields, &validator.FormErrorField{
ErrorField: "original_text",
ErrorMsg: reason.CommentContentCannotEmpty,
}), errors.BadRequest(reason.CommentContentCannotEmpty)
}
return nil, nil
}
// RemoveCommentReq remove comment
type RemoveCommentReq struct {
// comment id
CommentID string `validate:"required" json:"comment_id"`
// user id
UserID string `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
// UpdateCommentReq update comment request
type UpdateCommentReq struct {
// comment id
CommentID string `validate:"required" json:"comment_id"`
// original comment content
OriginalText string `validate:"required,notblank,gte=2,lte=600" json:"original_text"`
// parsed comment content
ParsedText string `json:"-"`
// user id
UserID string `json:"-"`
IsAdmin bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
req.ParsedText = converter.Markdown2HTML(req.OriginalText)
if req.ParsedText == "" {
return append(errFields, &validator.FormErrorField{
ErrorField: "original_text",
ErrorMsg: reason.CommentContentCannotEmpty,
}), errors.BadRequest(reason.CommentContentCannotEmpty)
}
return nil, nil
}
type UpdateCommentResp struct {
// comment id
CommentID string `json:"comment_id"`
// original comment content
OriginalText string `json:"original_text"`
// parsed comment content
ParsedText string `json:"parsed_text"`
}
// GetCommentListReq get comment list all request
type GetCommentListReq struct {
// user id
UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"`
// reply user id
ReplyUserID int64 `validate:"omitempty" comment:"reply user id" form:"reply_user_id"`
// reply comment id
ReplyCommentID int64 `validate:"omitempty" comment:"reply comment id" form:"reply_comment_id"`
// object id
ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"`
// user vote amount
VoteCount int `validate:"omitempty" comment:"user vote amount" form:"vote_count"`
// comment status(available: 0; deleted: 10)
Status int `validate:"omitempty" comment:"comment status(available: 0; deleted: 10)" form:"status"`
// original comment content
OriginalText string `validate:"omitempty" comment:"original comment content" form:"original_text"`
// parsed comment content
ParsedText string `validate:"omitempty" comment:"parsed comment content" form:"parsed_text"`
}
// GetCommentWithPageReq get comment list page request
type GetCommentWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// object id
ObjectID string `validate:"required" form:"object_id"`
// comment id
CommentID string `validate:"omitempty" form:"comment_id"`
// query condition
QueryCond string `validate:"omitempty,oneof=vote created_at" form:"query_cond"`
// user id
UserID string `json:"-"`
IsAdminModerator bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
}
// GetCommentReq get comment list page request
type GetCommentReq struct {
// object id
ID string `validate:"required" form:"id"`
// user id
UserID string `json:"-"`
IsAdminModerator bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
}
// GetCommentResp comment response
type GetCommentResp struct {
// comment id
CommentID string `json:"comment_id"`
// create time
CreatedAt int64 `json:"created_at"`
// object id
ObjectID string `json:"object_id"`
// user vote amount
VoteCount int `json:"vote_count"`
// current user if already vote this comment
IsVote bool `json:"is_vote"`
// original comment content
OriginalText string `json:"original_text"`
// parsed comment content
ParsedText string `json:"parsed_text"`
// user id
UserID string `json:"user_id"`
// username
Username string `json:"username"`
// user display name
UserDisplayName string `json:"user_display_name"`
// user avatar
UserAvatar string `json:"user_avatar"`
// user status
UserStatus string `json:"user_status"`
// reply user id
ReplyUserID string `json:"reply_user_id"`
// reply user username
ReplyUsername string `json:"reply_username"`
// reply user display name
ReplyUserDisplayName string `json:"reply_user_display_name"`
// reply comment id
ReplyCommentID string `json:"reply_comment_id"`
// reply user status
ReplyUserStatus string `json:"reply_user_status"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`
}
func (r *GetCommentResp) SetFromComment(comment *entity.Comment) {
_ = copier.Copy(r, comment)
r.CommentID = comment.ID
r.CreatedAt = comment.CreatedAt.Unix()
r.ReplyUserID = comment.GetReplyUserID()
r.ReplyCommentID = comment.GetReplyCommentID()
}
// GetCommentPersonalWithPageReq get comment list page request
type GetCommentPersonalWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// username
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
// user id
UserID string `json:"-"`
}
// GetCommentPersonalWithPageResp comment response
type GetCommentPersonalWithPageResp struct {
// comment id
CommentID string `json:"comment_id"`
// create time
CreatedAt int64 `json:"created_at"`
// object id
ObjectID string `json:"object_id"`
// question id
QuestionID string `json:"question_id"`
// answer id
AnswerID string `json:"answer_id"`
// object type
ObjectType string `json:"object_type" enums:"question,answer,tag,comment"`
// title
Title string `json:"title"`
// url title
UrlTitle string `json:"url_title"`
// content
Content string `json:"content"`
}
+74
View File
@@ -0,0 +1,74 @@
/*
* 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
// AddConfigReq add config request
type AddConfigReq struct {
// the config key
Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" json:"key"`
// the config value, custom data structures and types
Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" json:"value"`
}
// RemoveConfigReq delete config request
type RemoveConfigReq struct {
// config id
ID int `validate:"required" comment:"config id" json:"id"`
}
// UpdateConfigReq update config request
type UpdateConfigReq struct {
// config id
ID int `validate:"required" comment:"config id" json:"id"`
// the config key
Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" json:"key"`
// the config value, custom data structures and types
Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" json:"value"`
}
// GetConfigListReq get config list all request
type GetConfigListReq struct {
// the config key
Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" form:"key"`
// the config value, custom data structures and types
Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" form:"value"`
}
// GetConfigWithPageReq get config list page request
type GetConfigWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// the config key
Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" form:"key"`
// the config value, custom data structures and types
Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" form:"value"`
}
// GetConfigResp get config response
type GetConfigResp struct {
// config id
ID int `json:"id"`
// the config key
Key string `json:"key"`
// the config value, custom data structures and types
Value string `json:"value"`
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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
type ConnectorInfoResp struct {
Name string `json:"name"`
Icon string `json:"icon"`
Link string `json:"link"`
}
type ConnectorUserInfoResp struct {
Name string `json:"name"`
Icon string `json:"icon"`
Link string `json:"link"`
Binding bool `json:"binding"`
ExternalID string `json:"external_id"`
}
+66
View File
@@ -0,0 +1,66 @@
/*
* 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 "time"
var AppStartTime time.Time
const (
DashboardCacheKey = "answer:dashboard"
DashboardCacheTime = 60 * time.Minute
)
type DashboardInfo struct {
QuestionCount int64 `json:"question_count"`
ResolvedCount int64 `json:"resolved_count"`
ResolvedRate string `json:"resolved_rate"`
UnansweredCount int64 `json:"unanswered_count"`
UnansweredRate string `json:"unanswered_rate"`
AnswerCount int64 `json:"answer_count"`
CommentCount int64 `json:"comment_count"`
VoteCount int64 `json:"vote_count"`
UserCount int64 `json:"user_count"`
ReportCount int64 `json:"report_count"`
UploadingFiles bool `json:"uploading_files"`
SMTP string `json:"smtp"`
HTTPS bool `json:"https"`
TimeZone string `json:"time_zone"`
OccupyingStorageSpace string `json:"occupying_storage_space"`
AppStartTime string `json:"app_start_time"`
VersionInfo DashboardInfoVersion `json:"version_info"`
LoginRequired bool `json:"login_required"`
GoVersion string `json:"go_version"`
DatabaseVersion string `json:"database_version"`
DatabaseSize string `json:"database_size"`
}
type DashboardInfoVersion struct {
Version string `json:"version"`
Revision string `json:"revision"`
RemoteVersion string `json:"remote_version"`
}
type RemoteVersion struct {
Release struct {
Version string `json:"version"`
URL string `json:"url"`
} `json:"release"`
}
+145
View File
@@ -0,0 +1,145 @@
/*
* 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 (
"encoding/json"
"github.com/apache/answer/internal/base/constant"
)
const (
AccountActivationSourceType EmailSourceType = "account-activation"
PasswordResetSourceType EmailSourceType = "password-reset"
ConfirmNewEmailSourceType EmailSourceType = "password-reset"
UnsubscribeSourceType EmailSourceType = "unsubscribe"
BindingSourceType EmailSourceType = "binding"
)
type EmailSourceType string
type EmailCodeContent struct {
SourceType EmailSourceType `json:"source_type"`
Email string `json:"e_mail"`
UserID string `json:"user_id"`
// Used for unsubscribe notification
NotificationSources []constant.NotificationSource `json:"notification_source,omitempty"`
// Used for third-party login account binding
BindingKey string `json:"binding_key,omitempty"`
// Skip the validation of the latest code
SkipValidationLatestCode bool `json:"skip_validation_latest_code"`
}
func (r *EmailCodeContent) ToJSONString() string {
codeBytes, _ := json.Marshal(r)
return string(codeBytes)
}
func (r *EmailCodeContent) FromJSONString(data string) error {
return json.Unmarshal([]byte(data), &r)
}
type RegisterTemplateData struct {
SiteName string
RegisterUrl string
}
type PassResetTemplateData struct {
SiteName string
PassResetUrl string
}
type ChangeEmailTemplateData struct {
SiteName string
ChangeEmailUrl string
}
type TestTemplateData struct {
SiteName string
}
type NewAnswerTemplateRawData struct {
AnswerUserDisplayName string
QuestionTitle string
QuestionID string
AnswerID string
AnswerSummary string
UnsubscribeCode string
}
type NewAnswerTemplateData struct {
SiteName string
DisplayName string
QuestionTitle string
AnswerUrl string
AnswerSummary string
UnsubscribeUrl string
}
type NewInviteAnswerTemplateRawData struct {
InviterDisplayName string
QuestionTitle string
QuestionID string
UnsubscribeCode string
}
type NewInviteAnswerTemplateData struct {
SiteName string
DisplayName string
QuestionTitle string
InviteUrl string
UnsubscribeUrl string
}
type NewCommentTemplateRawData struct {
CommentUserDisplayName string
QuestionTitle string
QuestionID string
AnswerID string
CommentID string
CommentSummary string
UnsubscribeCode string
}
type NewCommentTemplateData struct {
SiteName string
DisplayName string
QuestionTitle string
CommentUrl string
CommentSummary string
UnsubscribeUrl string
}
type NewQuestionTemplateRawData struct {
QuestionAuthorUserID string
QuestionTitle string
QuestionID string
UnsubscribeCode string
Tags []string
TagIDs []string
}
type NewQuestionTemplateData struct {
SiteName string
QuestionTitle string
QuestionUrl string
Tags string
UnsubscribeUrl string
}
+30
View File
@@ -0,0 +1,30 @@
/*
* 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
type ErrTypeData struct {
ErrType string `json:"err_type"`
}
var ErrTypeModal = ErrTypeData{ErrType: "modal"}
var ErrTypeToast = ErrTypeData{ErrType: "toast"}
var ErrTypeAlert = ErrTypeData{ErrType: "alert"}
+114
View File
@@ -0,0 +1,114 @@
/*
* 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 (
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/pkg/uid"
)
// EventMsg event message
type EventMsg struct {
EventType constant.EventType
UserID string
TriggerObjectID string
QuestionID string
QuestionUserID string
AnswerID string
AnswerUserID string
CommentID string
CommentUserID string
ExtraInfo map[string]string
}
// NewEvent create a new event
func NewEvent(e constant.EventType, userID string) *EventMsg {
return &EventMsg{
UserID: userID,
EventType: e,
ExtraInfo: make(map[string]string),
}
}
// QID get question id
func (e *EventMsg) QID(questionID, userID string) *EventMsg {
if len(questionID) > 0 {
e.QuestionID = uid.DeShortID(questionID)
}
e.QuestionUserID = userID
return e
}
// AID get answer id
func (e *EventMsg) AID(answerID, userID string) *EventMsg {
if len(answerID) > 0 {
e.AnswerID = uid.DeShortID(answerID)
}
e.AnswerUserID = userID
return e
}
// CID get comment id
func (e *EventMsg) CID(comment, userID string) *EventMsg {
e.CommentID = comment
e.CommentUserID = userID
return e
}
// TID get trigger object id
func (e *EventMsg) TID(triggerObjectID string) *EventMsg {
if len(triggerObjectID) > 0 {
e.TriggerObjectID = uid.DeShortID(triggerObjectID)
}
return e
}
// AddExtra add extra info
func (e *EventMsg) AddExtra(key, value string) *EventMsg {
e.ExtraInfo[key] = value
return e
}
// GetExtra get extra info
func (e *EventMsg) GetExtra(key string) string {
if v, ok := e.ExtraInfo[key]; ok {
return v
}
return ""
}
// GetObjectID get object id
func (e *EventMsg) GetObjectID() string {
if len(e.TriggerObjectID) > 0 {
return e.TriggerObjectID
}
if len(e.CommentID) > 0 {
return e.CommentID
}
if len(e.AnswerID) > 0 {
return e.AnswerID
}
return e.QuestionID
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 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
// FollowReq follow object request
type FollowReq struct {
// object id
ObjectID string `validate:"required" form:"object_id" json:"object_id"`
// is cancel
IsCancel bool `validate:"omitempty" form:"is_cancel" json:"is_cancel"`
}
// FollowResp response object's follows and current user follow status
type FollowResp struct {
// the followers of object
Follows int `json:"follows"`
// if user is followed object will be true,otherwise false
IsFollowed bool `json:"is_followed"`
}
type FollowDTO struct {
// object TagID
ObjectID string
// is cancel
IsCancel bool
// user TagID
UserID string
}
// UpdateFollowTagsReq update user follow tags
type UpdateFollowTagsReq struct {
// tag slug name list
SlugNameList []string `json:"slug_name_list"`
// user id
UserID string `json:"-"`
}
+32
View File
@@ -0,0 +1,32 @@
/*
* 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
const (
ForbiddenReasonTypeInactive = "inactive"
ForbiddenReasonTypeURLExpired = "url_expired"
ForbiddenReasonTypeUserSuspended = "suspended"
)
// ForbiddenResp forbidden response
type ForbiddenResp struct {
// forbidden reason type
Type string `json:"type" enums:"inactive,url_expired"`
}
+238
View File
@@ -0,0 +1,238 @@
/*
* 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 (
"strings"
"github.com/apache/answer/pkg/converter"
"github.com/mark3labs/mcp-go/mcp"
)
const (
MCPSearchCondKeyword = "keyword"
MCPSearchCondUsername = "username"
MCPSearchCondScore = "score"
MCPSearchCondTag = "tag"
MCPSearchCondPage = "page"
MCPSearchCondPageSize = "page_size"
MCPSearchCondTagName = "tag_name"
MCPSearchCondQuestionID = "question_id"
MCPSearchCondObjectID = "object_id"
MCPSearchCondSemanticQuery = "query"
MCPSearchCondTopK = "top_k"
)
type MCPSearchCond struct {
Keyword string `json:"keyword"`
Username string `json:"username"`
Score int `json:"score"`
Tags []string `json:"tags"`
QuestionID string `json:"question_id"`
}
type MCPSearchQuestionDetail struct {
QuestionID string `json:"question_id"`
}
type MCPSearchCommentCond struct {
ObjectID string `json:"object_id"`
}
type MCPSearchTagCond struct {
TagName string `json:"tag_name"`
}
type MCPSearchUserCond struct {
Username string `json:"username"`
}
type MCPSearchQuestionInfoResp struct {
QuestionID string `json:"question_id"`
Title string `json:"title"`
Content string `json:"content"`
Link string `json:"link"`
}
type MCPSearchAnswerInfoResp struct {
QuestionID string `json:"question_id"`
QuestionTitle string `json:"question_title,omitempty"`
AnswerID string `json:"answer_id"`
AnswerContent string `json:"answer_content"`
Link string `json:"link"`
}
type MCPSearchTagResp struct {
TagName string `json:"tag_name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Link string `json:"link"`
}
type MCPSearchUserInfoResp struct {
Username string `json:"username"`
DisplayName string `json:"display_name"`
Avatar string `json:"avatar"`
Link string `json:"link"`
}
type MCPSearchCommentInfoResp struct {
CommentID string `json:"comment_id"`
Content string `json:"content"`
ObjectID string `json:"object_id"`
Link string `json:"link"`
}
// MCPSemanticSearchCond is the condition for semantic search.
type MCPSemanticSearchCond struct {
Query string `json:"query"`
TopK int `json:"top_k"`
}
// MCPSemanticSearchResp is a single semantic search result.
type MCPSemanticSearchResp struct {
ObjectID string `json:"object_id"`
ObjectType string `json:"object_type"`
Title string `json:"title"`
Content string `json:"content"`
Score float64 `json:"score"`
Link string `json:"link"`
Answers []*MCPSemanticSearchAnswer `json:"answers,omitempty"`
Comments []*MCPSemanticSearchComment `json:"comments,omitempty"`
}
// MCPSemanticSearchAnswer is an answer in a semantic search result.
type MCPSemanticSearchAnswer struct {
AnswerID string `json:"answer_id"`
Content string `json:"content"`
Comments []*MCPSemanticSearchComment `json:"comments,omitempty"`
}
// MCPSemanticSearchComment is a comment in a semantic search result.
type MCPSemanticSearchComment struct {
CommentID string `json:"comment_id"`
Content string `json:"content"`
}
func NewMCPSemanticSearchCond(request mcp.CallToolRequest) *MCPSemanticSearchCond {
cond := &MCPSemanticSearchCond{TopK: 5}
if query, ok := getRequestValue(request, MCPSearchCondSemanticQuery); ok {
cond.Query = query
}
if topK, ok := getRequestNumber(request, MCPSearchCondTopK); ok && topK > 0 {
cond.TopK = topK
}
return cond
}
func NewMCPSearchCond(request mcp.CallToolRequest) *MCPSearchCond {
cond := &MCPSearchCond{}
if keyword, ok := getRequestValue(request, MCPSearchCondKeyword); ok {
cond.Keyword = keyword
}
if username, ok := getRequestValue(request, MCPSearchCondUsername); ok {
cond.Username = username
}
if score, ok := getRequestNumber(request, MCPSearchCondScore); ok {
cond.Score = score
}
if tag, ok := getRequestValue(request, MCPSearchCondTag); ok {
cond.Tags = strings.Split(tag, ",")
}
if questionID, ok := getRequestValue(request, MCPSearchCondQuestionID); ok {
cond.QuestionID = questionID
}
return cond
}
func NewMCPSearchAnswerCond(request mcp.CallToolRequest) *MCPSearchCond {
cond := &MCPSearchCond{}
if questionID, ok := getRequestValue(request, MCPSearchCondQuestionID); ok {
cond.QuestionID = questionID
}
return cond
}
func NewMCPSearchQuestionDetail(request mcp.CallToolRequest) *MCPSearchQuestionDetail {
cond := &MCPSearchQuestionDetail{}
if questionID, ok := getRequestValue(request, MCPSearchCondQuestionID); ok {
cond.QuestionID = questionID
}
return cond
}
func NewMCPSearchCommentCond(request mcp.CallToolRequest) *MCPSearchCommentCond {
cond := &MCPSearchCommentCond{}
if keyword, ok := getRequestValue(request, MCPSearchCondObjectID); ok {
cond.ObjectID = keyword
}
return cond
}
func NewMCPSearchTagCond(request mcp.CallToolRequest) *MCPSearchTagCond {
cond := &MCPSearchTagCond{}
if tagName, ok := getRequestValue(request, MCPSearchCondTagName); ok {
cond.TagName = tagName
}
return cond
}
func NewMCPSearchUserCond(request mcp.CallToolRequest) *MCPSearchUserCond {
cond := &MCPSearchUserCond{}
if username, ok := getRequestValue(request, MCPSearchCondUsername); ok {
cond.Username = username
}
return cond
}
func getRequestValue(request mcp.CallToolRequest, key string) (string, bool) {
value, ok := request.GetArguments()[key].(string)
if !ok {
return "", false
}
return value, true
}
func getRequestNumber(request mcp.CallToolRequest, key string) (int, bool) {
value, ok := request.GetArguments()[key].(float64)
if !ok {
return 0, false
}
return int(value), true
}
func (cond *MCPSearchCond) ToQueryString() string {
var queryBuilder strings.Builder
if len(cond.Keyword) > 0 {
queryBuilder.WriteString(cond.Keyword)
}
if len(cond.Username) > 0 {
queryBuilder.WriteString(" user:" + cond.Username)
}
if cond.Score > 0 {
queryBuilder.WriteString(" score:" + converter.IntToString(int64(cond.Score)))
}
if len(cond.Tags) > 0 {
for _, tag := range cond.Tags {
queryBuilder.WriteString(" [" + tag + "]")
}
}
return strings.TrimSpace(queryBuilder.String())
}
+120
View File
@@ -0,0 +1,120 @@
/*
* 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 mcp_tools
import (
"github.com/apache/answer/internal/schema"
"github.com/mark3labs/mcp-go/mcp"
)
var (
MCPToolsList = []mcp.Tool{
NewQuestionsTool(),
NewAnswersTool(),
NewCommentsTool(),
NewTagsTool(),
NewTagDetailTool(),
NewUserTool(),
NewSemanticSearchTool(),
}
)
func NewQuestionsTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_questions",
mcp.WithDescription("Searching for questions that already existed in the system. After the search, you can use the get_answers_by_question_id tool to get answers for the questions."),
mcp.WithString(schema.MCPSearchCondKeyword,
mcp.Description("Keyword to search for questions. Multiple keywords separated by spaces"),
),
mcp.WithString(schema.MCPSearchCondUsername,
mcp.Description("Search for questions that contain only those created by the specified user"),
),
mcp.WithString(schema.MCPSearchCondTag,
mcp.Description("Filter by tag (semicolon separated for multiple tags)"),
),
mcp.WithString(schema.MCPSearchCondScore,
mcp.Description("Minimum score that the question must have"),
),
)
return listFilesTool
}
func NewAnswersTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_answers_by_question_id",
mcp.WithDescription("Search for all answers corresponding to the question ID. The question ID is provided by get_questions tool."),
mcp.WithString(schema.MCPSearchCondQuestionID,
mcp.Description("The ID of the question to which the answer belongs. The question ID is provided by get_questions tool."),
),
)
return listFilesTool
}
func NewCommentsTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_comments",
mcp.WithDescription("Searching for comments that already existed in the system"),
mcp.WithString(schema.MCPSearchCondObjectID,
mcp.Description("Queries comments on an object, either a question or an answer. object_id is the id of the object."),
),
)
return listFilesTool
}
func NewTagsTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_tags",
mcp.WithDescription("Searching for tags that already existed in the system"),
mcp.WithString(schema.MCPSearchCondTagName,
mcp.Description("Tag name"),
),
)
return listFilesTool
}
func NewTagDetailTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_tag_detail",
mcp.WithDescription("Get detailed information about a specific tag"),
mcp.WithString(schema.MCPSearchCondTagName,
mcp.Description("Tag name"),
),
)
return listFilesTool
}
func NewUserTool() mcp.Tool {
listFilesTool := mcp.NewTool("get_user",
mcp.WithDescription("Searching for users that already existed in the system"),
mcp.WithString(schema.MCPSearchCondUsername,
mcp.Description("Username"),
),
)
return listFilesTool
}
func NewSemanticSearchTool() mcp.Tool {
tool := mcp.NewTool("semantic_search",
mcp.WithDescription("Search questions and answers by semantic meaning. Use this when the user's question relates conceptually to existing content but may not match exact keywords. Returns the most semantically similar content."),
mcp.WithString(schema.MCPSearchCondSemanticQuery,
mcp.Required(),
mcp.Description("The search query text to find semantically similar questions and answers"),
),
mcp.WithNumber(schema.MCPSearchCondTopK,
mcp.Description("Maximum number of results to return (default 5)"),
),
)
return tool
}
+115
View File
@@ -0,0 +1,115 @@
/*
* 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 "slices"
type UpdateReactionReq struct {
ObjectID string `validate:"required" json:"object_id"`
Emoji string `validate:"required,oneof=heart smile frown" json:"emoji"`
Reaction string `validate:"required,oneof=activate deactivate" json:"reaction"`
UserID string `json:"-"`
}
type GetReactionReq struct {
ObjectID string `validate:"required" form:"object_id"`
UserID string `json:"-"`
}
// ReactionsSummaryMeta reactions summary meta
type ReactionsSummaryMeta struct {
Reactions []*ReactionSummaryMeta `json:"reactions"`
}
// ReactionSummaryMeta reaction summary meta
type ReactionSummaryMeta struct {
Emoji string `json:"emoji"`
UserIDs []string `json:"user_ids"`
}
// AddReactionSummary add user operation to reaction summary
func (r *ReactionsSummaryMeta) AddReactionSummary(emoji, userID string) {
for _, reaction := range r.Reactions {
if reaction.Emoji != emoji {
continue
}
exist := slices.Contains(reaction.UserIDs, userID)
if !exist {
reaction.UserIDs = append(reaction.UserIDs, userID)
}
return
}
r.Reactions = append(r.Reactions, &ReactionSummaryMeta{
Emoji: emoji,
UserIDs: []string{userID},
})
}
// RemoveReactionSummary remove user operation from reaction summary
func (r *ReactionsSummaryMeta) RemoveReactionSummary(emoji, userID string) {
updatedReactions := make([]*ReactionSummaryMeta, 0)
for _, reaction := range r.Reactions {
if reaction.Emoji != emoji && len(reaction.UserIDs) > 0 {
updatedReactions = append(updatedReactions, reaction)
continue
}
updatedUserIDs := make([]string, 0, len(r.Reactions))
for _, id := range reaction.UserIDs {
if id != userID {
updatedUserIDs = append(updatedUserIDs, id)
}
}
if len(updatedUserIDs) > 0 {
reaction.UserIDs = updatedUserIDs
updatedReactions = append(updatedReactions, reaction)
}
}
r.Reactions = updatedReactions
}
// CheckUserInReactionSummary check user's operation if in reaction summary
func (r *ReactionsSummaryMeta) CheckUserInReactionSummary(emoji, userID string) bool {
for _, reaction := range r.Reactions {
if reaction.Emoji != emoji {
continue
}
if slices.Contains(reaction.UserIDs, userID) {
return true
}
}
return false
}
// GetReactionByObjectIdResp get reaction by object id response
type GetReactionByObjectIdResp struct {
ReactionSummary []*ReactionRespItem `json:"reaction_summary"`
}
// ReactionRespItem reaction response item
type ReactionRespItem struct {
// Emoji is the reaction emoji
Emoji string `json:"emoji"`
// Count is the number of users who reacted
Count int `json:"count"`
// Tooltip is the user's name who reacted
Tooltip string `json:"tooltip"`
// IsActive is if current user has reacted
IsActive bool `json:"is_active"`
}
@@ -0,0 +1,53 @@
/*
* 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 (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/pkg/uid"
)
type ExternalNotificationMsg struct {
ReceiverUserID string `json:"receiver_user_id"`
ReceiverEmail string `json:"receiver_email"`
ReceiverLang string `json:"receiver_lang"`
NewAnswerTemplateRawData *NewAnswerTemplateRawData `json:"new_answer_template_raw_data,omitempty"`
NewInviteAnswerTemplateRawData *NewInviteAnswerTemplateRawData `json:"new_invite_answer_template_raw_data,omitempty"`
NewCommentTemplateRawData *NewCommentTemplateRawData `json:"new_comment_template_raw_data,omitempty"`
NewQuestionTemplateRawData *NewQuestionTemplateRawData `json:"new_question_template_raw_data,omitempty"`
}
func CreateNewQuestionNotificationMsg(
questionID, questionTitle, questionAuthorUserID string, tags []*entity.Tag) *ExternalNotificationMsg {
questionID = uid.DeShortID(questionID)
msg := &ExternalNotificationMsg{
NewQuestionTemplateRawData: &NewQuestionTemplateRawData{
QuestionAuthorUserID: questionAuthorUserID,
QuestionID: questionID,
QuestionTitle: questionTitle,
},
}
for _, tag := range tags {
msg.NewQuestionTemplateRawData.Tags = append(msg.NewQuestionTemplateRawData.Tags, tag.SlugName)
msg.NewQuestionTemplateRawData.TagIDs = append(msg.NewQuestionTemplateRawData.TagIDs, tag.ID)
}
return msg
}
+192
View File
@@ -0,0 +1,192 @@
/*
* 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 (
"encoding/json"
"sort"
"github.com/apache/answer/internal/entity"
)
const (
NotificationTypeInbox = 1
NotificationTypeAchievement = 2
NotificationNotRead = 1
NotificationRead = 2
NotificationStatusNormal = 1
NotificationStatusDelete = 10
NotificationInboxTypeAll = 0
NotificationInboxTypePosts = 1
NotificationInboxTypeVotes = 2
NotificationInboxTypeInvites = 3
)
var NotificationType = map[string]int{
"inbox": NotificationTypeInbox,
"achievement": NotificationTypeAchievement,
}
var NotificationInboxType = map[string]int{
"all": NotificationInboxTypeAll,
"posts": NotificationInboxTypePosts,
"invites": NotificationInboxTypeInvites,
"votes": NotificationInboxTypeVotes,
}
type NotificationContent struct {
ID string `json:"id"`
TriggerUserID string `json:"-"` // show userid
ReceiverUserID string `json:"-"` // receiver userid
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
ObjectInfo ObjectInfo `json:"object_info"`
Rank int `json:"rank"`
NotificationAction string `json:"notification_action,omitempty"`
Type int `json:"-"` // 1 inbox 2 achievement
IsRead bool `json:"is_read"`
UpdateTime int64 `json:"update_time"`
}
type GetRedDot struct {
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
CanReviewTag bool `json:"-"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
// NotificationMsg notification message
type NotificationMsg struct {
// trigger notification user id
TriggerUserID string
// receive notification user id
ReceiverUserID string
// type 1 inbox 2 achievement
Type int
// notification title
Title string
// notification object
ObjectID string
// notification object type
ObjectType string
// notification action
NotificationAction string
// if true no need to send notification to all followers
NoNeedPushAllFollow bool
// extra info
ExtraInfo map[string]string
}
type ObjectInfo struct {
Title string `json:"title"`
ObjectID string `json:"object_id"`
ObjectMap map[string]string `json:"object_map"`
ObjectType string `json:"object_type"`
}
type RedDot struct {
Inbox int64 `json:"inbox"`
Achievement int64 `json:"achievement"`
Revision int64 `json:"revision"`
CanRevision bool `json:"can_revision"`
BadgeAward *RedDotBadgeAward `json:"badge_award"`
}
type RedDotBadgeAward struct {
NotificationID string `json:"notification_id"`
BadgeID string `json:"badge_id"`
Name string `json:"name"`
Icon string `json:"icon"`
Level entity.BadgeLevel `json:"level"`
}
type RedDotBadgeAwardCache struct {
BadgeAwardList map[string]*RedDotBadgeAward `json:"badge_award_list"`
}
// NewRedDotBadgeAwardCache new red dot badge award cache
func NewRedDotBadgeAwardCache() *RedDotBadgeAwardCache {
return &RedDotBadgeAwardCache{
BadgeAwardList: make(map[string]*RedDotBadgeAward),
}
}
// GetBadgeAward get badge award
func (r *RedDotBadgeAwardCache) GetBadgeAward() *RedDotBadgeAward {
if len(r.BadgeAwardList) == 0 {
return nil
}
var ids []string
for _, v := range r.BadgeAwardList {
ids = append(ids, v.NotificationID)
}
sort.Strings(ids)
return r.BadgeAwardList[ids[0]]
}
// FromJSON from json
func (r *RedDotBadgeAwardCache) FromJSON(data string) {
_ = json.Unmarshal([]byte(data), r)
}
// ToJSON to json
func (r *RedDotBadgeAwardCache) ToJSON() string {
data, _ := json.Marshal(r)
return string(data)
}
// AddBadgeAward add badge award
func (r *RedDotBadgeAwardCache) AddBadgeAward(badgeAward *RedDotBadgeAward) {
if r.BadgeAwardList == nil {
r.BadgeAwardList = make(map[string]*RedDotBadgeAward)
}
r.BadgeAwardList[badgeAward.NotificationID] = badgeAward
}
// RemoveBadgeAward remove badge award
func (r *RedDotBadgeAwardCache) RemoveBadgeAward(notificationID string) {
if r.BadgeAwardList == nil {
return
}
delete(r.BadgeAwardList, notificationID)
}
type NotificationSearch struct {
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
Type int `json:"-" form:"-"`
TypeStr string `json:"type" form:"type"` // inbox achievement
InboxTypeStr string `json:"inbox_type" form:"inbox_type"` // inbox achievement
InboxType int `json:"-" form:"-"` // inbox achievement
UserID string `json:"-"`
}
type NotificationClearRequest struct {
NotificationType string `validate:"required,oneof=inbox achievement" json:"type"`
UserID string `json:"-"`
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
CanReviewTag bool `json:"-"`
}
type NotificationClearIDRequest struct {
UserID string `json:"-"`
ID string `json:"id" form:"id"`
}
+73
View File
@@ -0,0 +1,73 @@
/*
* 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 (
"strings"
"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/i18n"
)
// PermissionTrTplData template data as for translate permission message
type PermissionTrTplData struct {
Rank int
}
// PermissionMemberAction permission member action
type PermissionMemberAction struct {
Action string `json:"action"`
Name string `json:"name"`
Type string `json:"type"`
}
// GetPermissionReq get permission request
type GetPermissionReq struct {
Action string `form:"action"`
Actions []string `validate:"omitempty" form:"actions"`
}
func (r *GetPermissionReq) Check() (errField []*validator.FormErrorField, err error) {
if len(r.Action) > 0 {
r.Actions = strings.Split(r.Action, ",")
}
return nil, nil
}
// GetPermissionResp get permission response
type GetPermissionResp struct {
HasPermission bool `json:"has_permission"`
// only not allow, will return this tip
NoPermissionTip string `json:"no_permission_tip"`
}
func (r *GetPermissionResp) TrTip(lang i18n.Language, requireRank int) {
if r.HasPermission {
return
}
if requireRank <= 0 {
r.NoPermissionTip = translator.Tr(lang, reason.RankFailToMeetTheCondition)
} else {
r.NoPermissionTip = translator.TrWithData(
lang, reason.NoEnoughRankToOperate, &PermissionTrTplData{Rank: requireRank})
}
}
+169
View File
@@ -0,0 +1,169 @@
/*
* 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 (
"github.com/apache/answer/plugin"
"github.com/gin-gonic/gin"
)
const (
PluginStatusActive PluginStatus = "active"
PluginStatusInactive PluginStatus = "inactive"
)
type PluginStatus string
type GetPluginListReq struct {
Status PluginStatus `form:"status"`
HaveConfig bool `form:"have_config"`
}
type GetPluginListResp struct {
Name string `json:"name"`
SlugName string `json:"slug_name"`
Description string `json:"description"`
Version string `json:"version"`
Enabled bool `json:"enabled"`
HaveConfig bool `json:"have_config"`
Link string `json:"link"`
}
type GetAllPluginStatusResp struct {
SlugName string `json:"slug_name"`
Enabled bool `json:"enabled"`
}
type UpdatePluginStatusReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
Enabled bool `json:"enabled"`
}
type GetPluginConfigReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" form:"plugin_slug_name"`
}
type GetPluginConfigResp struct {
Name string `json:"name"`
SlugName string `json:"slug_name"`
Description string `json:"description"`
Version string `json:"version"`
ConfigFields []ConfigField `json:"config_fields"`
}
func (g *GetPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin.ConfigField) {
for _, field := range fields {
configField := ConfigField{
Name: field.Name,
Type: string(field.Type),
Title: field.Title.Translate(ctx),
Description: field.Description.Translate(ctx),
Required: field.Required,
Value: field.Value,
UIOptions: ConfigFieldUIOptions{
Rows: field.UIOptions.Rows,
InputType: string(field.UIOptions.InputType),
Variant: field.UIOptions.Variant,
ClassName: field.UIOptions.ClassName,
FieldClassName: field.UIOptions.FieldClassName,
},
}
configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx)
configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx)
configField.UIOptions.Text = field.UIOptions.Text.Translate(ctx)
if field.UIOptions.Action != nil {
uiOptionAction := &UIOptionAction{
Url: field.UIOptions.Action.Url,
Method: field.UIOptions.Action.Method,
}
if field.UIOptions.Action.Loading != nil {
uiOptionAction.Loading = &LoadingAction{
Text: field.UIOptions.Action.Loading.Text.Translate(ctx),
State: string(field.UIOptions.Action.Loading.State),
}
}
if field.UIOptions.Action.OnComplete != nil {
uiOptionAction.OnCompleteAction = &OnCompleteAction{
ToastReturnMessage: field.UIOptions.Action.OnComplete.ToastReturnMessage,
RefreshFormConfig: field.UIOptions.Action.OnComplete.RefreshFormConfig,
}
}
configField.UIOptions.Action = uiOptionAction
}
for _, option := range field.Options {
configField.Options = append(configField.Options, ConfigFieldOption{
Label: option.Label.Translate(ctx),
Value: option.Value,
})
}
g.ConfigFields = append(g.ConfigFields, configField)
}
}
type ConfigField struct {
Name string `json:"name"`
Type string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
Required bool `json:"required"`
Value any `json:"value"`
UIOptions ConfigFieldUIOptions `json:"ui_options"`
Options []ConfigFieldOption `json:"options,omitempty"`
}
type ConfigFieldUIOptions struct {
Placeholder string `json:"placeholder,omitempty"`
Rows string `json:"rows,omitempty"`
InputType string `json:"input_type,omitempty"`
Label string `json:"label,omitempty"`
Action *UIOptionAction `json:"action,omitempty"`
Variant string `json:"variant,omitempty"`
Text string `json:"text,omitempty"`
ClassName string `json:"class_name,omitempty"`
FieldClassName string `json:"field_class_name,omitempty"`
}
type ConfigFieldOption struct {
Label string `json:"label"`
Value string `json:"value"`
}
type UIOptionAction struct {
Url string `json:"url"`
Method string `json:"method,omitempty"`
Loading *LoadingAction `json:"loading,omitempty"`
OnCompleteAction *OnCompleteAction `json:"on_complete,omitempty"`
}
type LoadingAction struct {
Text string `json:"text"`
State string `json:"state"`
}
type OnCompleteAction struct {
ToastReturnMessage bool `json:"toast_return_message"`
RefreshFormConfig bool `json:"refresh_form_config"`
}
type UpdatePluginConfigReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
ConfigFields map[string]any `json:"config_fields"`
}
+54
View File
@@ -0,0 +1,54 @@
/*
* 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
type UserCenterAgentResp struct {
Enabled bool `json:"enabled"`
AgentInfo *AgentInfo `json:"agent_info"`
}
type AgentInfo struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Icon string `json:"icon"`
Url string `json:"url"`
LoginRedirectURL string `json:"login_redirect_url"`
SignUpRedirectURL string `json:"sign_up_redirect_url"`
ControlCenterItems []*ControlCenter `json:"control_center"`
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
}
type ControlCenter struct {
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
type UserCenterPersonalBranding struct {
Enabled bool `json:"enabled"`
PersonalBranding []*PersonalBranding `json:"personal_branding"`
}
type PersonalBranding struct {
Icon string `json:"icon"`
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
+102
View File
@@ -0,0 +1,102 @@
/*
* 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 (
"github.com/apache/answer/plugin"
"github.com/gin-gonic/gin"
)
type GetUserPluginListResp struct {
Name string `json:"name"`
SlugName string `json:"slug_name"`
}
type UpdateUserPluginReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
UserID string `json:"-"`
}
type GetUserPluginConfigReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" form:"plugin_slug_name"`
UserID string `json:"-"`
}
type GetUserPluginConfigResp struct {
Name string `json:"name"`
SlugName string `json:"slug_name"`
ConfigFields []*ConfigField `json:"config_fields"`
}
func (g *GetUserPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin.ConfigField) {
for _, field := range fields {
configField := &ConfigField{
Name: field.Name,
Type: string(field.Type),
Title: field.Title.Translate(ctx),
Description: field.Description.Translate(ctx),
Required: field.Required,
Value: field.Value,
UIOptions: ConfigFieldUIOptions{
Rows: field.UIOptions.Rows,
InputType: string(field.UIOptions.InputType),
Variant: field.UIOptions.Variant,
ClassName: field.UIOptions.ClassName,
FieldClassName: field.UIOptions.FieldClassName,
},
}
configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx)
configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx)
configField.UIOptions.Text = field.UIOptions.Text.Translate(ctx)
if field.UIOptions.Action != nil {
uiOptionAction := &UIOptionAction{
Url: field.UIOptions.Action.Url,
Method: field.UIOptions.Action.Method,
}
if field.UIOptions.Action.Loading != nil {
uiOptionAction.Loading = &LoadingAction{
Text: field.UIOptions.Action.Loading.Text.Translate(ctx),
State: string(field.UIOptions.Action.Loading.State),
}
}
if field.UIOptions.Action.OnComplete != nil {
uiOptionAction.OnCompleteAction = &OnCompleteAction{
ToastReturnMessage: field.UIOptions.Action.OnComplete.ToastReturnMessage,
RefreshFormConfig: field.UIOptions.Action.OnComplete.RefreshFormConfig,
}
}
configField.UIOptions.Action = uiOptionAction
}
for _, option := range field.Options {
configField.Options = append(configField.Options, ConfigFieldOption{
Label: option.Label.Translate(ctx),
Value: option.Value,
})
}
g.ConfigFields = append(g.ConfigFields, configField)
}
}
type UpdateUserPluginConfigReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
ConfigFields map[string]any `json:"config_fields"`
UserID string `json:"-"`
}
+528
View File
@@ -0,0 +1,528 @@
/*
* 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 (
"strings"
"time"
"github.com/apache/answer/internal/base/reason"
"github.com/segmentfault/pacman/errors"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/pkg/converter"
"github.com/apache/answer/pkg/uid"
)
const (
QuestionOperationPin = "pin"
QuestionOperationUnPin = "unpin"
QuestionOperationHide = "hide"
QuestionOperationShow = "show"
)
// RemoveQuestionReq delete question request
type RemoveQuestionReq struct {
// question id
ID string `validate:"required" json:"id"`
UserID string `json:"-" ` // user_id
IsAdmin bool `json:"-"`
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
type CloseQuestionReq struct {
ID string `validate:"required" json:"id"`
CloseType int `json:"close_type"` // close_type
CloseMsg string `json:"close_msg"` // close_type
UserID string `json:"-"` // user_id
}
type OperationQuestionReq struct {
ID string `validate:"required" json:"id"`
Operation string `json:"operation"` // operation [pin unpin hide show]
UserID string `json:"-"` // user_id
CanPin bool `json:"-"`
CanList bool `json:"-"`
}
type CloseQuestionMeta struct {
CloseType int `json:"close_type"`
CloseMsg string `json:"close_msg"`
}
// ReopenQuestionReq reopen question request
type ReopenQuestionReq struct {
QuestionID string `json:"question_id"`
UserID string `json:"-"`
}
type QuestionAdd struct {
// question title
Title string `validate:"required,notblank,gte=6,lte=150" json:"title"`
// content
Content string `validate:"gte=0,lte=65535" json:"content"`
// html
HTML string `json:"-"`
// tags
Tags []*TagItem `validate:"dive" json:"tags"`
// user id
UserID string `json:"-"`
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
IP string `json:"-"`
UserAgent string `json:"-"`
}
func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err error) {
req.HTML = converter.Markdown2HTML(req.Content)
for _, tag := range req.Tags {
if len(tag.OriginalText) > 0 {
tag.ParsedText = converter.Markdown2HTML(tag.OriginalText)
}
}
return nil, nil
}
type QuestionAddByAnswer struct {
// question title
Title string `validate:"required,notblank,gte=6,lte=150" json:"title"`
// content
Content string `validate:"gte=0,lte=65535" json:"content"`
// html
HTML string `json:"-"`
AnswerContent string `validate:"required,notblank,gte=6,lte=65535" json:"answer_content"`
AnswerHTML string `json:"-"`
// tags
Tags []*TagItem `validate:"dive" json:"tags"`
// user id
UserID string `json:"-"`
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
IP string `json:"-"`
UserAgent string `json:"-"`
}
func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, err error) {
req.HTML = converter.Markdown2HTML(req.Content)
req.AnswerHTML = converter.Markdown2HTML(req.AnswerContent)
for _, tag := range req.Tags {
if len(tag.OriginalText) > 0 {
tag.ParsedText = converter.Markdown2HTML(tag.OriginalText)
}
}
if req.AnswerHTML == "" {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "answer_content",
ErrorMsg: reason.AnswerContentCannotEmpty,
})
return errFields, errors.BadRequest(reason.QuestionContentCannotEmpty)
}
return nil, nil
}
type QuestionPermission struct {
IsAdminModerator bool `json:"-"`
// whether user can add it
CanAdd bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
// whether user can close it
CanClose bool `json:"-"`
// whether user can reopen it
CanReopen bool `json:"-"`
// whether user can pin it
CanPin bool `json:"-"`
CanUnPin bool `json:"-"`
// whether user can hide it
CanHide bool `json:"-"`
CanShow bool `json:"-"`
// whether user can use reserved it
CanUseReservedTag bool `json:"-"`
// whether user can invite other user to answer this question
CanInviteOtherToAnswer bool `json:"-"`
CanAddTag bool `json:"-"`
CanRecover bool `json:"-"`
}
type CheckCanQuestionUpdate struct {
// question id
ID string `validate:"required" form:"id"`
// user id
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
type QuestionUpdate struct {
// question id
ID string `validate:"required" json:"id"`
// question title
Title string `validate:"required,notblank,gte=6,lte=150" json:"title"`
// content
Content string `validate:"gte=0,lte=65535" json:"content"`
// html
HTML string `json:"-"`
InviteUser []string `validate:"omitempty" json:"invite_user"`
// tags
Tags []*TagItem `validate:"dive" json:"tags"`
// edit summary
EditSummary string `validate:"omitempty" json:"edit_summary"`
// user id
UserID string `json:"-"`
NoNeedReview bool `json:"-"`
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
type QuestionRecoverReq struct {
QuestionID string `validate:"required" json:"question_id"`
UserID string `json:"-"`
}
type QuestionUpdateInviteUser struct {
ID string `validate:"required" json:"id"`
InviteUser []string `validate:"omitempty" json:"invite_user"`
UserID string `json:"-"`
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) {
req.HTML = converter.Markdown2HTML(req.Content)
return nil, nil
}
type QuestionBaseInfo struct {
ID string `json:"id" `
Title string `json:"title"`
UrlTitle string `json:"url_title"`
ViewCount int `json:"view_count"`
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
FollowCount int `json:"follow_count"`
Status string `json:"status"`
AcceptedAnswer bool `json:"accepted_answer"`
}
type QuestionInfoResp struct {
ID string `json:"id" `
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Content string `json:"content"`
HTML string `json:"html"`
Description string `json:"description"`
Tags []*TagResp `json:"tags"`
ViewCount int `json:"view_count"`
UniqueViewCount int `json:"unique_view_count"`
VoteCount int `json:"vote_count"`
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
FollowCount int `json:"follow_count"`
AcceptedAnswerID string `json:"accepted_answer_id"`
LastAnswerID string `json:"last_answer_id"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"-"`
PostUpdateTime int64 `json:"update_time"`
QuestionUpdateTime int64 `json:"edit_time"`
Pin int `json:"pin"`
Show int `json:"show"`
Status int `json:"status"`
Operation *Operation `json:"operation,omitempty"`
UserID string `json:"-"`
LastEditUserID string `json:"-"`
LastAnsweredUserID string `json:"-"`
UserInfo *UserBasicInfo `json:"user_info"`
UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"`
LastAnsweredUserInfo *UserBasicInfo `json:"last_answered_user_info,omitempty"`
Answered bool `json:"answered"`
FirstAnswerId string `json:"first_answer_id"`
Collected bool `json:"collected"`
VoteStatus string `json:"vote_status"`
IsFollowed bool `json:"is_followed"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`
ExtendsActions []*PermissionMemberAction `json:"extends_actions"`
}
// UpdateQuestionResp update question resp
type UpdateQuestionResp struct {
UrlTitle string `json:"url_title"`
WaitForReview bool `json:"wait_for_review"`
}
type AdminQuestionInfo struct {
ID string `json:"id"`
Title string `json:"title"`
VoteCount int `json:"vote_count"`
Show int `json:"show"`
Pin int `json:"pin"`
AnswerCount int `json:"answer_count"`
AcceptedAnswerID string `json:"accepted_answer_id"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
EditTime int64 `json:"edit_time"`
UserID string `json:"-" `
UserInfo *UserBasicInfo `json:"user_info"`
}
type OperationLevel string
const (
OperationLevelInfo OperationLevel = "info"
OperationLevelDanger OperationLevel = "danger"
OperationLevelWarning OperationLevel = "warning"
OperationLevelSecondary OperationLevel = "secondary"
)
type Operation struct {
Type string `json:"type"`
Description string `json:"description"`
Msg string `json:"msg"`
Time int64 `json:"time"`
Level OperationLevel `json:"level"`
}
type GetCloseTypeResp struct {
// report name
Name string `json:"name"`
// report description
Description string `json:"description"`
// report source
Source string `json:"source"`
// report type
Type int `json:"type"`
// is have content
HaveContent bool `json:"have_content"`
// content type
ContentType string `json:"content_type"`
}
type UserAnswerInfo struct {
AnswerID string `json:"answer_id"`
QuestionID string `json:"question_id"`
Accepted int `json:"accepted"`
VoteCount int `json:"vote_count"`
CreateTime int `json:"create_time"`
UpdateTime int `json:"update_time"`
QuestionInfo struct {
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Tags []any `json:"tags"`
} `json:"question_info"`
}
type UserQuestionInfo struct {
ID string `json:"question_id"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
VoteCount int `json:"vote_count"`
Tags []any `json:"tags"`
ViewCount int `json:"view_count"`
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
CreatedAt int64 `json:"created_at"`
AcceptedAnswerID string `json:"accepted_answer_id"`
Status string `json:"status"`
}
const (
QuestionOrderCondNewest = "newest"
QuestionOrderCondActive = "active"
QuestionOrderCondHot = "hot"
QuestionOrderCondScore = "score"
QuestionOrderCondUnanswered = "unanswered"
QuestionOrderCondRecommend = "recommend"
QuestionOrderCondFrequent = "frequent"
// HotInDays limit max days of the hottest question
HotInDays = 90
)
// QuestionPageReq query questions page
type QuestionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend frequent" form:"order"`
Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"`
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
InDays int `validate:"omitempty,min=1" form:"in_days"`
LoginUserID string `json:"-"`
UserIDBeSearched string `json:"-"`
TagID string `json:"-"`
ShowPending bool `json:"-"`
}
const (
QuestionPageRespOperationTypeAsked = "asked"
QuestionPageRespOperationTypeAnswered = "answered"
QuestionPageRespOperationTypeModified = "modified"
)
type QuestionPageResp struct {
ID string `json:"id" `
CreatedAt int64 `json:"created_at"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Description string `json:"description"`
Pin int `json:"pin"` // 1: unpin, 2: pin
Show int `json:"show"` // 0: show, 1: hide
Status int `json:"status"`
Tags []*TagResp `json:"tags"`
// question statistical information
ViewCount int `json:"view_count"`
UniqueViewCount int `json:"unique_view_count"`
VoteCount int `json:"vote_count"`
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
FollowCount int `json:"follow_count"`
// answer information
AcceptedAnswerID string `json:"accepted_answer_id"`
LastAnswerID string `json:"last_answer_id"`
LastAnsweredUserID string `json:"-"`
LastAnsweredAt time.Time `json:"-"`
// operator information
OperatedAt int64 `json:"operated_at"`
Operator *QuestionPageRespOperator `json:"operator"`
OperationType string `json:"operation_type"`
}
type QuestionPageRespOperator struct {
ID string `json:"id"`
Username string `json:"username"`
Rank int `json:"rank"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
Avatar string `json:"avatar"`
}
type AdminQuestionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
StatusCond string `validate:"omitempty,oneof=normal closed deleted pending" form:"status"`
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" `
Status int `json:"-"`
LoginUserID string `json:"-"`
}
func (req *AdminQuestionPageReq) Check() (errField []*validator.FormErrorField, err error) {
status, ok := entity.AdminQuestionSearchStatus[req.StatusCond]
if ok {
req.Status = status
}
if req.Status == 0 {
req.Status = 1
}
return nil, nil
}
// AdminAnswerPageReq admin answer page req
type AdminAnswerPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
StatusCond string `validate:"omitempty,oneof=normal deleted pending" form:"status"`
Query string `validate:"omitempty,gt=0,lte=100" form:"query"`
QuestionID string `validate:"omitempty,gt=0,lte=24" form:"question_id"`
QuestionTitle string `json:"-"`
AnswerID string `json:"-"`
Status int `json:"-"`
LoginUserID string `json:"-"`
}
func (req *AdminAnswerPageReq) Check() (errField []*validator.FormErrorField, err error) {
req.QuestionID = uid.DeShortID(req.QuestionID)
if req.QuestionID == "0" {
req.QuestionID = ""
}
if status, ok := entity.AdminAnswerSearchStatus[req.StatusCond]; ok {
req.Status = status
}
if req.Status == 0 {
req.Status = 1
}
// parse query condition
if len(req.Query) > 0 {
prefix := "answer:"
if strings.Contains(req.Query, prefix) {
req.AnswerID = uid.DeShortID(strings.TrimSpace(strings.TrimPrefix(req.Query, prefix)))
} else {
req.QuestionTitle = strings.TrimSpace(req.Query)
}
}
return nil, nil
}
type AdminUpdateQuestionStatusReq struct {
QuestionID string `validate:"required" json:"question_id"`
Status string `validate:"required,oneof=available closed deleted" json:"status"`
UserID string `json:"-"`
}
type PersonalQuestionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered" form:"order"`
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
LoginUserID string `json:"-"`
IsAdmin bool `json:"-"`
}
type PersonalAnswerPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered" form:"order"`
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
LoginUserID string `json:"-"`
IsAdmin bool `json:"-"`
}
type PersonalCollectionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
UserID string `json:"-"`
}
type GetQuestionLinkReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1,max=100" form:"page_size"`
QuestionID string `validate:"required" form:"question_id"`
OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend frequent" form:"order"`
InDays int `validate:"omitempty,min=1" form:"in_days"`
LoginUserID string `json:"-"`
}
type GetQuestionLinkResp struct {
QuestionPageResp
}
+56
View File
@@ -0,0 +1,56 @@
/*
* 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
// GetRankPersonalWithPageReq get rank list page request
type GetRankPersonalWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// username
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
// user id
UserID string `json:"-"`
}
// GetRankPersonalPageResp rank response
type GetRankPersonalPageResp struct {
// create time
CreatedAt int64 `json:"created_at"`
// object id
ObjectID string `json:"object_id"`
// question id
QuestionID string `json:"question_id"`
// answer id
AnswerID string `json:"answer_id"`
// object type
ObjectType string `json:"object_type" enums:"question,answer,tag,comment"`
// title
Title string `json:"title"`
// url title
UrlTitle string `json:"url_title"`
// content
Content string `json:"content"`
// reputation
Reputation int `json:"reputation"`
// rank type
RankType string `json:"rank_type"`
}
+63
View File
@@ -0,0 +1,63 @@
/*
* 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 (
"github.com/apache/answer/internal/base/translator"
"github.com/segmentfault/pacman/i18n"
)
type ReasonItem struct {
ReasonKey string `json:"reason_key"`
ReasonType int `json:"reason_type"`
Name string `json:"name"`
Description string `json:"description"`
ContentType string `json:"content_type"`
Placeholder string `json:"placeholder"`
}
type ReasonReq struct {
// ObjectType
ObjectType string `validate:"required" form:"object_type" json:"object_type"`
// Action
Action string `validate:"required" form:"action" json:"action"`
}
func (r *ReasonItem) Translate(keyPrefix string, lang i18n.Language) {
trField := func(fieldName, fieldData string) string {
// If fieldData is empty, means no need to translate
if len(fieldData) == 0 {
return fieldData
}
key := keyPrefix + "." + fieldName
fieldTr := translator.Tr(lang, key)
if fieldTr != key {
// If i18n key exists, return i18n value
return fieldTr
}
// If i18n key not exists, return fieldData original value
return fieldData
}
r.ReasonKey = keyPrefix
r.Name = trField("name", r.Name)
r.Description = trField("desc", r.Description)
r.Placeholder = trField("placeholder", r.Placeholder)
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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
// PostRenderReq post render request
type PostRenderReq struct {
Content string `json:"content"`
}
+115
View File
@@ -0,0 +1,115 @@
/*
* 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
// AddReportReq add report request
type AddReportReq struct {
// object id
ObjectID string `validate:"required,gt=0,lte=20" json:"object_id"`
// report type
ReportType int `validate:"required" json:"report_type"`
// report content
Content string `validate:"omitempty,gt=0,lte=500" json:"content"`
// user id
UserID string `json:"-"`
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
// GetReportListReq get report list all request
type GetReportListReq struct {
// report source
Source string `validate:"required,oneof=question answer comment" form:"source"`
}
// GetReportTypeResp get report response
type GetReportTypeResp struct {
// report name
Name string `json:"name"`
// report description
Description string `json:"description"`
// report source
Source string `json:"source"`
// report type
Type int `json:"type"`
// is have content
HaveContent bool `json:"have_content"`
// content type
ContentType string `json:"content_type"`
}
// ReportHandleReq request handle request
type ReportHandleReq struct {
ID string `validate:"required" comment:"report id" form:"id" json:"id"`
FlaggedType int `validate:"required" comment:"flagged type" form:"flagged_type" json:"flagged_type"`
FlaggedContent string `validate:"omitempty" comment:"flagged content" form:"flagged_content" json:"flagged_content"`
}
// GetReportListPageDTO report list data transfer object
type GetReportListPageDTO struct {
Page int
PageSize int
Status int
}
// GetReportListPageResp get report list
type GetReportListPageResp struct {
FlagID string `json:"flag_id"`
CreatedAt int64 `json:"created_at"`
ObjectID string `json:"object_id"`
QuestionID string `json:"question_id"`
AnswerID string `json:"answer_id"`
CommentID string `json:"comment_id"`
ObjectType string `json:"object_type" enums:"question,answer,comment"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
OriginalText string `json:"original_text"`
ParsedText string `json:"parsed_text"`
AnswerCount int `json:"answer_count"`
AnswerAccepted bool `json:"answer_accepted"`
Tags []*TagResp `json:"tags"`
ObjectStatus int `json:"object_status"`
ObjectShowStatus int `json:"object_show_status"`
AuthorUserInfo UserBasicInfo `json:"author_user_info"`
SubmitAt int64 `json:"submit_at"`
SubmitterUser UserBasicInfo `json:"submitter_user"`
Reason *ReasonItem `json:"reason"`
ReasonContent string `json:"reason_content"`
}
// GetUnreviewedReportPostPageReq get unreviewed report post page request
type GetUnreviewedReportPostPageReq struct {
Page int `json:"page" form:"page"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
// ReviewReportReq review report request
type ReviewReportReq struct {
FlagID string `validate:"required" json:"flag_id"`
OperationType string `validate:"required,oneof=edit_post close_post delete_post unlist_post ignore_report" json:"operation_type"`
CloseType int `validate:"omitempty" json:"close_type"`
CloseMsg string `validate:"omitempty" json:"close_msg"`
Title string `validate:"omitempty,notblank,gte=6,lte=150" json:"title"`
Content string `validate:"omitempty,notblank,gte=6,lte=65535" json:"content"`
Tags []*TagItem `validate:"omitempty,dive" json:"tags"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
+80
View File
@@ -0,0 +1,80 @@
/*
* 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 (
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/pkg/uid"
)
// UpdateReviewReq update review request
type UpdateReviewReq struct {
ReviewID int `validate:"required" json:"review_id"`
Status string `validate:"required,oneof=approve reject" json:"status"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
func (r *UpdateReviewReq) IsApprove() bool {
return r.Status == "approve"
}
func (r *UpdateReviewReq) IsReject() bool {
return r.Status == "reject"
}
// GetUnreviewedPostPageReq get review page request
type GetUnreviewedPostPageReq struct {
ObjectID string `validate:"omitempty" form:"object_id"`
Page int `validate:"omitempty" form:"page"`
ReviewerMapping map[string]string `json:"-"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
func (r *GetUnreviewedPostPageReq) Check() (errField []*validator.FormErrorField, err error) {
if len(r.ObjectID) > 0 {
r.Page = 1
r.ObjectID = uid.DeShortID(r.ObjectID)
}
return
}
// GetUnreviewedPostPageResp get review page response
type GetUnreviewedPostPageResp struct {
ReviewID int `json:"review_id"`
CreatedAt int64 `json:"created_at"`
ObjectID string `json:"object_id"`
QuestionID string `json:"question_id"`
AnswerID string `json:"answer_id"`
CommentID string `json:"comment_id"`
ObjectType string `json:"object_type" enums:"question,answer,comment"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
OriginalText string `json:"original_text"`
ParsedText string `json:"parsed_text"`
Tags []*TagResp `json:"tags"`
ObjectStatus int `json:"object_status"`
ObjectShowStatus int `json:"object_show_status"`
AuthorUserInfo UserBasicInfo `json:"author_user_info"`
SubmitAt int64 `json:"submit_at"`
SubmitterDisplayName string `json:"submitter_display_name"`
Reason string `json:"reason"`
}
+138
View File
@@ -0,0 +1,138 @@
/*
* 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 (
"time"
"github.com/apache/answer/internal/base/constant"
)
// AddRevisionDTO add revision request
type AddRevisionDTO struct {
// user id
UserID string
// object id
ObjectID string
// title
Title string
// content
Content string
// log
Log string
// status
Status int
}
// GetRevisionListReq get revision list all request
type GetRevisionListReq struct {
// object id
ObjectID string `validate:"required" comment:"object_id" form:"object_id"`
IsAdmin bool `json:"-"`
UserID string `json:"-"`
}
const RevisionAuditApprove = "approve"
const RevisionAuditReject = "reject"
type RevisionAuditReq struct {
// object id
ID string `validate:"required" comment:"id" form:"id"`
Operation string `validate:"required" comment:"operation" form:"operation"` // approve or reject
UserID string `json:"-"`
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
CanReviewTag bool `json:"-"`
}
type RevisionSearch struct {
Page int `json:"page" form:"page"` // Query number of pages
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
CanReviewTag bool `json:"-"`
UserID string `json:"-"`
}
func (r RevisionSearch) GetCanReviewObjectTypes() []int {
objectType := make([]int, 0)
if r.CanReviewAnswer {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.AnswerObjectType])
}
if r.CanReviewQuestion {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.QuestionObjectType])
}
if r.CanReviewTag {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.TagObjectType])
}
return objectType
}
type GetUnreviewedRevisionResp struct {
Type string `json:"type"`
Info *UnreviewedRevisionInfoInfo `json:"info"`
UnreviewedInfo *GetRevisionResp `json:"unreviewed_info"`
}
// GetRevisionResp get revision response
type GetRevisionResp struct {
ID string `json:"id"`
UserID string `json:"use_id"`
ObjectID string `json:"object_id"`
ObjectType int `json:"-"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Content string `json:"-"`
ContentParsed any `json:"content"`
Status int `json:"status"`
CreatedAt time.Time `json:"-"`
CreatedAtParsed int64 `json:"create_at"`
UserInfo UserBasicInfo `json:"user_info"`
Log string `json:"reason"`
}
// GetReviewingTypeReq get reviewing type request
type GetReviewingTypeReq struct {
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
CanReviewTag bool `json:"-"`
IsAdmin bool `json:"-"`
UserID string `json:"-"`
}
func (r *GetReviewingTypeReq) GetCanReviewObjectTypes() []int {
objectType := make([]int, 0)
if r.CanReviewAnswer {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.AnswerObjectType])
}
if r.CanReviewQuestion {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.QuestionObjectType])
}
if r.CanReviewTag {
objectType = append(objectType, constant.ObjectTypeStrMapping[constant.TagObjectType])
}
return objectType
}
// GetReviewingTypeResp get reviewing type response
type GetReviewingTypeResp struct {
Name string `json:"name"`
Label string `json:"label"`
TodoAmount int64 `json:"todo_amount"`
}
+27
View File
@@ -0,0 +1,27 @@
/*
* 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
// GetRoleResp get role response
type GetRoleResp struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
+190
View File
@@ -0,0 +1,190 @@
/*
* 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 (
"regexp"
"strings"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/plugin"
)
type SearchDTO struct {
Query string `validate:"required,gte=1,lte=60" form:"q"`
Page int `validate:"omitempty,min=1" form:"page,default=1"`
Size int `validate:"omitempty,min=1,max=50" form:"size,default=30"`
Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" enums:"newest,active,score,relevance"`
CaptchaID string `form:"captcha_id"`
CaptchaCode string `form:"captcha_code"`
UserID string `json:"-"`
}
func (s *SearchDTO) Check() (errField []*validator.FormErrorField, err error) {
// Replace special characters.
// Special characters will cause the search abnormal, such as search for "#" will get nearly all the content that Markdown format.
replacedContent, patterns := ReplaceSearchContent(s.Query)
s.Query = strings.Join(append(patterns, replacedContent), " ")
return nil, nil
}
func ReplaceSearchContent(content string) (string, []string) {
// Define the regular expressions for key:value pairs and [tag]
keyValueRegex := regexp.MustCompile(`\w+:\S+`)
tagRegex := regexp.MustCompile(`\[\w+\]`)
// Define the pattern for characters to replace
replaceCharsPattern := regexp.MustCompile(`[+#.<>\-_()*]`)
// Extract key:value pairs
keyValues := keyValueRegex.FindAllString(content, -1)
// Extract [tag]
tags := tagRegex.FindAllString(content, -1)
// Replace key:value pairs and [tag] with empty string
contentWithoutPatterns := keyValueRegex.ReplaceAllString(content, "")
contentWithoutPatterns = tagRegex.ReplaceAllString(contentWithoutPatterns, "")
// Replace characters with pattern [+#.<>_()*] with space
replacedContent := replaceCharsPattern.ReplaceAllString(contentWithoutPatterns, " ")
return strings.TrimSpace(replacedContent), append(keyValues, tags...)
}
type SearchCondition struct {
// search target type: all/question/answer
TargetType string
// search query user id
UserID string
// vote amount
VoteAmount int
// only show not accepted answer's question
NotAccepted bool
// view amount
Views int
// answer count
AnswerAmount int
// only show accepted answer
Accepted bool
// only show this question's answer
QuestionID string
// search query tags
Tags [][]string
// search query keywords
Words []string
}
// SearchAll check if search all
func (s *SearchCondition) SearchAll() bool {
return len(s.TargetType) == 0
}
// SearchQuestion check if search only need question
func (s *SearchCondition) SearchQuestion() bool {
return s.TargetType == constant.QuestionObjectType
}
// SearchAnswer check if search only need answer
func (s *SearchCondition) SearchAnswer() bool {
return s.TargetType == constant.AnswerObjectType
}
// Convert2PluginSearchCond convert to plugin search condition
func (s *SearchCondition) Convert2PluginSearchCond(page, pageSize int, order string) *plugin.SearchBasicCond {
basic := &plugin.SearchBasicCond{
Page: page,
PageSize: pageSize,
Words: s.Words,
TagIDs: s.Tags,
UserID: s.UserID,
Order: plugin.SearchOrderCond(order),
QuestionID: s.QuestionID,
VoteAmount: s.VoteAmount,
ViewAmount: s.Views,
AnswerAmount: s.AnswerAmount,
}
if s.Accepted {
basic.AnswerAccepted = plugin.AcceptedCondTrue
} else {
basic.AnswerAccepted = plugin.AcceptedCondAll
}
if s.NotAccepted {
basic.QuestionAccepted = plugin.AcceptedCondFalse
} else {
basic.QuestionAccepted = plugin.AcceptedCondAll
}
return basic
}
type SearchObject struct {
ID string `json:"id"`
QuestionID string `json:"question_id"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Excerpt string `json:"excerpt"`
CreatedAtParsed int64 `json:"created_at"`
VoteCount int `json:"vote_count"`
Accepted bool `json:"accepted"`
AnswerCount int `json:"answer_count"`
// user info
UserInfo *SearchObjectUser `json:"user_info"`
// tags
Tags []*TagResp `json:"tags"`
// Status
StatusStr string `json:"status"`
}
type SearchObjectUser struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
Rank int `json:"rank"`
Status string `json:"status"`
}
type TagResp struct {
ID string `json:"-"`
SlugName string `json:"slug_name"`
DisplayName string `json:"display_name"`
// if main tag slug name is not empty, this tag is synonymous with the main tag
MainTagSlugName string `json:"main_tag_slug_name"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
type SearchResult struct {
// object_type
ObjectType string `json:"object_type"`
// this object
Object *SearchObject `json:"object"`
}
type SearchResp struct {
Total int64 `json:"count"`
// search response
SearchResults []*SearchResult `json:"list"`
}
type SearchDescResp struct {
Name string `json:"name"`
Icon string `json:"icon"`
Link string `json:"link"`
}
+41
View File
@@ -0,0 +1,41 @@
/*
* 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 (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestReplaceSearchContent(t *testing.T) {
content := "user:aaa [tag] ssssfdfdf-as#fsadf"
replacedContent, patterns := ReplaceSearchContent(content)
ret := strings.Join(append(patterns, replacedContent), " ")
assert.Equal(t, "user:aaa [tag] ssssfdfdf as fsadf", ret)
content = "user:aaa-sss [tag1] ssssfdfdf-as#fsadf [tag2] score:3"
replacedContent, patterns = ReplaceSearchContent(content)
ret = strings.Join(append(patterns, replacedContent), " ")
assert.Equal(t, "user:aaa-sss score:3 [tag1] [tag2] ssssfdfdf as fsadf", ret)
}
+162
View File
@@ -0,0 +1,162 @@
/*
* 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 (
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/entity"
"github.com/segmentfault/pacman/errors"
)
// SimpleObjectInfo simple object info
type SimpleObjectInfo struct {
ObjectID string `json:"object_id"`
ObjectCreatorUserID string `json:"object_creator_user_id"`
QuestionID string `json:"question_id"`
QuestionCreatorUserID string `json:"question_creator_user_id"`
QuestionStatus int `json:"question_status"`
QuestionShow int `json:"question_show"`
AnswerID string `json:"answer_id"`
AnswerStatus int `json:"answer_status"`
CommentID string `json:"comment_id"`
CommentStatus int `json:"comment_status"`
TagID string `json:"tag_id"`
TagStatus int `json:"tag_status"`
ObjectType string `json:"object_type"`
Title string `json:"title"`
Content string `json:"content"`
}
// IsDeleted is deleted
func (s *SimpleObjectInfo) IsDeleted() bool {
switch s.ObjectType {
case constant.QuestionObjectType:
return s.QuestionStatus == entity.QuestionStatusDeleted
case constant.AnswerObjectType:
return s.AnswerStatus == entity.AnswerStatusDeleted
case constant.CommentObjectType:
return s.CommentStatus == entity.CommentStatusDeleted
case constant.TagObjectType:
return s.TagStatus == entity.TagStatusDeleted
}
return false
}
func (s *SimpleObjectInfo) CheckVisibility(userID string, isAdminModerator bool) error {
if s == nil {
return errors.NotFound(reason.ObjectNotFound)
}
if s.isObjectRestricted() && !s.canViewObject(userID, isAdminModerator) {
return errors.NotFound(s.objectNotFoundReason())
}
if s.hasParentQuestion() && s.isParentQuestionRestricted() &&
!s.canViewParentQuestion(userID, isAdminModerator) {
return errors.NotFound(reason.QuestionNotFound)
}
return nil
}
func (s *SimpleObjectInfo) canViewObject(userID string, isAdminModerator bool) bool {
if isAdminModerator {
return true
}
switch s.ObjectType {
case constant.QuestionObjectType:
return s.QuestionCreatorUserID == userID
case constant.AnswerObjectType, constant.CommentObjectType, constant.TagObjectType:
return s.ObjectCreatorUserID == userID
default:
return false
}
}
func (s *SimpleObjectInfo) canViewParentQuestion(userID string, isAdminModerator bool) bool {
if isAdminModerator {
return true
}
return s.QuestionCreatorUserID == userID
}
func (s *SimpleObjectInfo) hasParentQuestion() bool {
switch s.ObjectType {
case constant.AnswerObjectType, constant.CommentObjectType:
return len(s.QuestionID) > 0 && s.QuestionID != "0"
default:
return false
}
}
func (s *SimpleObjectInfo) isObjectRestricted() bool {
switch s.ObjectType {
case constant.QuestionObjectType:
return s.QuestionStatus == entity.QuestionStatusDeleted ||
s.QuestionStatus == entity.QuestionStatusPending ||
s.QuestionShow == entity.QuestionHide
case constant.AnswerObjectType:
return s.AnswerStatus == entity.AnswerStatusDeleted || s.AnswerStatus == entity.AnswerStatusPending
case constant.CommentObjectType:
return s.CommentStatus == entity.CommentStatusDeleted || s.CommentStatus == entity.CommentStatusPending
case constant.TagObjectType:
return s.TagStatus == entity.TagStatusDeleted
default:
return false
}
}
func (s *SimpleObjectInfo) isParentQuestionRestricted() bool {
return s.QuestionStatus == entity.QuestionStatusDeleted ||
s.QuestionStatus == entity.QuestionStatusPending ||
s.QuestionShow == entity.QuestionHide
}
func (s *SimpleObjectInfo) objectNotFoundReason() string {
switch s.ObjectType {
case constant.QuestionObjectType:
return reason.QuestionNotFound
case constant.AnswerObjectType:
return reason.AnswerNotFound
case constant.CommentObjectType:
return reason.CommentNotFound
case constant.TagObjectType:
return reason.TagNotFound
default:
return reason.ObjectNotFound
}
}
type UnreviewedRevisionInfoInfo struct {
CreatedAt int64 `json:"created_at"`
ObjectID string `json:"object_id"`
QuestionID string `json:"question_id"`
AnswerID string `json:"answer_id"`
CommentID string `json:"comment_id"`
ObjectType string `json:"object_type"`
ObjectCreatorUserID string `json:"object_creator_user_id"`
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Content string `json:"content"`
Html string `json:"html"`
AnswerCount int `json:"answer_count"`
AnswerAccepted bool `json:"answer_accepted"`
Tags []*TagResp `json:"tags"`
Status int `json:"status"`
ShowStatus int `json:"show_status"`
}
+596
View File
@@ -0,0 +1,596 @@
/*
* 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,
}
}
+72
View File
@@ -0,0 +1,72 @@
/*
* 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 (
"encoding/json"
"testing"
"github.com/apache/answer/internal/base/validator"
"github.com/segmentfault/pacman/i18n"
"github.com/stretchr/testify/require"
)
func TestSiteLoginReqRequireEmailVerificationValidation(t *testing.T) {
tests := []struct {
name string
payload string
expectError bool
}{
{
name: "omitted is invalid",
payload: `{"allow_new_registrations":true,"allow_email_registrations":true,"allow_password_login":true,"allow_email_domains":[]}`,
expectError: true,
},
{
name: "null is invalid",
payload: `{"allow_new_registrations":true,"allow_email_registrations":true,"allow_password_login":true,"allow_email_domains":[],"require_email_verification":null}`,
expectError: true,
},
{
name: "false is valid",
payload: `{"allow_new_registrations":true,"allow_email_registrations":true,"allow_password_login":true,"allow_email_domains":[],"require_email_verification":false}`,
expectError: false,
},
{
name: "true is valid",
payload: `{"allow_new_registrations":true,"allow_email_registrations":true,"allow_password_login":true,"allow_email_domains":[],"require_email_verification":true}`,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &SiteLoginReq{}
require.NoError(t, json.Unmarshal([]byte(tt.payload), req))
_, err := validator.GetValidatorByLang(i18n.DefaultLanguage).Check(req)
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* 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
type SiteMapList struct {
QuestionIDs []*SiteMapQuestionInfo `json:"question_ids"`
MaxPageNum []int `json:"max_page_num"`
}
type SiteMapPageList struct {
PageData []*SiteMapQuestionInfo `json:"page_data"`
}
type SiteMapQuestionInfo struct {
ID string `json:"id"`
Title string `json:"title"`
UpdateTime string `json:"time"`
}
+84
View File
@@ -0,0 +1,84 @@
/*
* 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
// AddTagListReq add tag list request
type AddTagListReq struct {
// tag_id
TagID int64 `validate:"required" comment:"tag_id" json:"tag_id"`
// object_id
ObjectID int64 `validate:"required" comment:"object_id" json:"object_id"`
// tag_list_status(available: 1; deleted: 10)
Status int `validate:"required" comment:"tag_list_status(available: 1; deleted: 10)" json:"status"`
}
// RemoveTagListReq delete tag list request
type RemoveTagListReq struct {
// tag_list_id
ID int64 `validate:"required" comment:"tag_list_id" json:"id"`
}
// UpdateTagListReq update tag list request
type UpdateTagListReq struct {
// tag_list_id
ID int64 `validate:"required" comment:"tag_list_id" json:"id"`
// tag_id
TagID int64 `validate:"omitempty" comment:"tag_id" json:"tag_id"`
// object_id
ObjectID int64 `validate:"omitempty" comment:"object_id" json:"object_id"`
// tag_list_status(available: 1; deleted: 10)
Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" json:"status"`
}
// GetTagListListReq get tag list list all request
type GetTagListListReq struct {
// tag_id
TagID int64 `validate:"omitempty" comment:"tag_id" form:"tag_id"`
// object_id
ObjectID int64 `validate:"omitempty" comment:"object_id" form:"object_id"`
// tag_list_status(available: 1; deleted: 10)
Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" form:"status"`
}
// GetTagListWithPageReq get tag list list page request
type GetTagListWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// tag_id
TagID int64 `validate:"omitempty" comment:"tag_id" form:"tag_id"`
// object_id
ObjectID int64 `validate:"omitempty" comment:"object_id" form:"object_id"`
// tag_list_status(available: 1; deleted: 10)
Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" form:"status"`
}
// GetTagListResp get tag list response
type GetTagListResp struct {
// tag_list_id
ID int64 `json:"id"`
// tag_id
TagID int64 `json:"tag_id"`
// object_id
ObjectID int64 `json:"object_id"`
// tag_list_status(available: 1; deleted: 10)
Status int `json:"status"`
}
+323
View File
@@ -0,0 +1,323 @@
/*
* 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 (
"strings"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/pkg/converter"
)
// SearchTagLikeReq get tag list all request
type SearchTagLikeReq struct {
// tag
Tag string `validate:"omitempty" form:"tag"`
IsAdmin bool `json:"-"`
}
// SearchTagsBySlugName search tags by slug name
type SearchTagsBySlugName struct {
// slug name list split by ','
Tags string `form:"tags"`
}
// GetTagInfoReq get tag info request
type GetTagInfoReq struct {
// tag id
ID string `validate:"omitempty" form:"id"`
// tag slug name
Name string `validate:"omitempty,gt=0,lte=35" form:"name"`
UserID string `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanMerge bool `json:"-"`
CanRecover bool `json:"-"`
}
type GetTamplateTagInfoReq struct {
// tag id
ID string `validate:"omitempty" form:"id"`
// tag slug name
Name string `validate:"omitempty" form:"name"`
// user id
UserID string `json:"-"`
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
}
func (r *GetTagInfoReq) Check() (errFields []*validator.FormErrorField, err error) {
r.Name = strings.ToLower(r.Name)
return nil, nil
}
// GetTagResp get tag response
type GetTagResp struct {
TagID string `json:"tag_id"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
SlugName string `json:"slug_name"`
DisplayName string `json:"display_name"`
Excerpt string `json:"excerpt"`
OriginalText string `json:"original_text"`
ParsedText string `json:"parsed_text"`
Description string `json:"description"`
FollowCount int `json:"follow_count"`
QuestionCount int `json:"question_count"`
IsFollower bool `json:"is_follower"`
Status string `json:"status"`
MemberActions []*PermissionMemberAction `json:"member_actions"`
// if main tag slug name is not empty, this tag is synonymous with the main tag
MainTagSlugName string `json:"main_tag_slug_name"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
func (tr *GetTagResp) GetExcerpt() {
excerpt := strings.TrimSpace(tr.ParsedText)
idx := strings.Index(excerpt, "\n")
if idx >= 0 {
excerpt = excerpt[0:idx]
}
tr.Excerpt = excerpt
}
// GetTagPageResp get tag response
type GetTagPageResp struct {
// tag_id
TagID string `json:"tag_id"`
// slug_name
SlugName string `json:"slug_name"`
// display_name
DisplayName string `json:"display_name"`
// excerpt
Excerpt string `json:"excerpt"`
// description
Description string `json:"description"`
// original text
OriginalText string `json:"original_text"`
// parsed_text
ParsedText string `json:"parsed_text"`
// follower amount
FollowCount int `json:"follow_count"`
// question amount
QuestionCount int `json:"question_count"`
// is follower
IsFollower bool `json:"is_follower"`
// created time
CreatedAt int64 `json:"created_at"`
// updated time
UpdatedAt int64 `json:"updated_at"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
func (tr *GetTagPageResp) GetExcerpt() {
excerpt := strings.TrimSpace(tr.ParsedText)
idx := strings.Index(excerpt, "\n")
if idx >= 0 {
excerpt = excerpt[0:idx]
}
tr.Excerpt = excerpt
}
type TagChange struct {
ObjectID string `json:"object_id"` // object_id
Tags []*TagItem `json:"tags"` // tags name
// user id
UserID string `json:"-"`
}
type TagItem struct {
// slug_name
SlugName string `validate:"omitempty,gt=0,lte=35" json:"slug_name"`
// display_name
DisplayName string `validate:"omitempty,gt=0,lte=35" json:"display_name"`
// original text
OriginalText string `validate:"omitempty" json:"original_text"`
// parsed text
ParsedText string `json:"-"`
}
// RemoveTagReq delete tag request
type RemoveTagReq struct {
// tag_id
TagID string `validate:"required" json:"tag_id"`
// user id
UserID string `json:"-"`
}
// AddTagReq add tag request
type AddTagReq struct {
// slug_name
SlugName string `validate:"required,gt=0,lte=35" json:"slug_name"`
// display_name
DisplayName string `validate:"required,gt=0,lte=35" json:"display_name"`
// original text
OriginalText string `validate:"required,gt=0,lte=65536" json:"original_text"`
// parsed text
ParsedText string `json:"-"`
// user id
UserID string `json:"-"`
}
func (req *AddTagReq) Check() (errFields []*validator.FormErrorField, err error) {
req.ParsedText = converter.Markdown2HTML(req.OriginalText)
req.SlugName = strings.ToLower(req.SlugName)
return nil, nil
}
// AddTagResp add tag response
type AddTagResp struct {
SlugName string `json:"slug_name"`
}
// UpdateTagReq update tag request
type UpdateTagReq struct {
// tag_id
TagID string `validate:"required" json:"tag_id"`
// slug_name
SlugName string `validate:"omitempty,gt=0,lte=35" json:"slug_name"`
// display_name
DisplayName string `validate:"omitempty,gt=0,lte=35" json:"display_name"`
// original text
OriginalText string `validate:"omitempty" json:"original_text"`
// parsed text
ParsedText string `json:"-"`
// edit summary
EditSummary string `validate:"omitempty" json:"edit_summary"`
// user id
UserID string `json:"-"`
NoNeedReview bool `json:"-"`
}
func (r *UpdateTagReq) Check() (errFields []*validator.FormErrorField, err error) {
r.ParsedText = converter.Markdown2HTML(r.OriginalText)
return nil, nil
}
// RecoverTagReq update tag request
type RecoverTagReq struct {
TagID string `validate:"required" json:"tag_id"`
UserID string `json:"-"`
}
// UpdateTagResp update tag response
type UpdateTagResp struct {
WaitForReview bool `json:"wait_for_review"`
}
// GetTagWithPageReq get tag list page request
type GetTagWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// slug_name
SlugName string `validate:"omitempty,gt=0,lte=35" form:"slug_name"`
// display_name
DisplayName string `validate:"omitempty,gt=0,lte=35" form:"display_name"`
// query condition
QueryCond string `validate:"omitempty,oneof=popular name newest" form:"query_cond"`
// user id
UserID string `json:"-"`
}
// GetTagSynonymsReq get tag synonyms request
type GetTagSynonymsReq struct {
// tag_id
TagID string `validate:"required" form:"tag_id"`
// user id
UserID string `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
}
// GetTagSynonymsResp get tag synonyms response
type GetTagSynonymsResp struct {
// synonyms
Synonyms []*TagSynonym `json:"synonyms"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`
}
type TagSynonym struct {
// tag id
TagID string `json:"tag_id"`
// slug name
SlugName string `json:"slug_name"`
// display name
DisplayName string `json:"display_name"`
// if main tag slug name is not empty, this tag is synonymous with the main tag
MainTagSlugName string `json:"main_tag_slug_name"`
}
// UpdateTagSynonymReq update tag request
type UpdateTagSynonymReq struct {
// tag_id
TagID string `validate:"required" json:"tag_id"`
// synonym tag list
SynonymTagList []*TagItem `validate:"required,dive" json:"synonym_tag_list"`
// user id
UserID string `json:"-"`
}
func (req *UpdateTagSynonymReq) Format() {
for _, item := range req.SynonymTagList {
item.SlugName = strings.ToLower(item.SlugName)
}
}
// GetFollowingTagsResp get following tags response
type GetFollowingTagsResp struct {
// tag id
TagID string `json:"tag_id"`
// slug name
SlugName string `json:"slug_name"`
// display name
DisplayName string `json:"display_name"`
// if main tag slug name is not empty, this tag is synonymous with the main tag
MainTagSlugName string `json:"main_tag_slug_name"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
// GetTagBasicResp get tag basic response
type GetTagBasicResp struct {
TagID string `json:"tag_id"`
SlugName string `json:"slug_name"`
DisplayName string `json:"display_name"`
Recommend bool `json:"recommend"`
Reserved bool `json:"reserved"`
}
// MergeTagReq merge tag request
type MergeTagReq struct {
// source tag id
SourceTagID string `validate:"required" json:"source_tag_id"`
// target tag id
TargetTagID string `validate:"required" json:"target_tag_id"`
// user id
UserID string `json:"-"`
}
// MergeTagResp merge tag response
type MergeTagResp struct {
}
+76
View File
@@ -0,0 +1,76 @@
/*
* 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 "time"
type Paginator struct {
Pages []int
Totalpages int
Prevpage int
Nextpage int
Currpage int
}
type QAPageJsonLD struct {
Context string `json:"@context"`
Type string `json:"@type"`
MainEntity struct {
Type string `json:"@type"`
Name string `json:"name"`
Text string `json:"text"`
AnswerCount int `json:"answerCount"`
UpvoteCount int `json:"upvoteCount"`
DateCreated time.Time `json:"dateCreated"`
Author struct {
URL string `json:"url"`
Type string `json:"@type"`
Name string `json:"name"`
} `json:"author"`
AcceptedAnswer *AcceptedAnswerItem `json:"acceptedAnswer,omitempty"`
SuggestedAnswer []*SuggestedAnswerItem `json:"suggestedAnswer"`
} `json:"mainEntity"`
}
type AcceptedAnswerItem struct {
Type string `json:"@type"`
Text string `json:"text"`
DateCreated time.Time `json:"dateCreated"`
UpvoteCount int `json:"upvoteCount"`
URL string `json:"url"`
Author struct {
URL string `json:"url"`
Type string `json:"@type"`
Name string `json:"name"`
} `json:"author"`
}
type SuggestedAnswerItem struct {
Type string `json:"@type"`
Text string `json:"text"`
DateCreated time.Time `json:"dateCreated"`
UpvoteCount int `json:"upvoteCount"`
URL string `json:"url"`
Author struct {
URL string `json:"url"`
Type string `json:"@type"`
Name string `json:"name"`
} `json:"author"`
}
+27
View File
@@ -0,0 +1,27 @@
/*
* 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
var GetThemeOptions = []*ThemeOption{
{
Label: "Default",
Value: "default",
},
}
@@ -0,0 +1,112 @@
/*
* 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
const (
ExternalLoginOAuthStateLoginIntent = "login"
ExternalLoginOAuthStateBindIntent = "bind"
)
// UserExternalLoginResp user external login resp
type UserExternalLoginResp struct {
BindingKey string `json:"binding_key"`
AccessToken string `json:"access_token"`
// ErrMsg error message, if not empty, means login failed and this message should be displayed.
ErrMsg string `json:"-"`
ErrTitle string `json:"-"`
}
// ExternalLoginBindingUserSendEmailReq external login binding user request
type ExternalLoginBindingUserSendEmailReq struct {
BindingKey string `validate:"required,gt=1,lte=100" json:"binding_key"`
Email string `validate:"required,gt=1,lte=512,email" json:"email"`
// If must is true, whatever email if exists, try to bind user.
// If must is false, when email exist, will only be prompted with a warning.
Must bool `json:"must"`
}
// ExternalLoginBindingUserSendEmailResp external login binding user response
type ExternalLoginBindingUserSendEmailResp struct {
EmailExistAndMustBeConfirmed bool `json:"email_exist_and_must_be_confirmed"`
AccessToken string `json:"access_token"`
}
// ExternalLoginBindingUserReq external login binding user request
type ExternalLoginBindingUserReq struct {
Code string `validate:"required,gt=0,lte=500" json:"code"`
Content string `json:"-"`
}
// ExternalLoginBindingUserResp external login binding user response
type ExternalLoginBindingUserResp struct {
AccessToken string `json:"access_token"`
}
// ExternalLoginUserInfoCache external login user info
type ExternalLoginUserInfoCache struct {
// Third party identification
// e.g. facebook, twitter, instagram
Provider string
// required. The unique user ID provided by the third-party login
ExternalID string
// optional. This name is used preferentially during registration
DisplayName string
// optional. This username is used preferentially during registration
Username string
// optional. If email exist will bind the existing user
Email string
// optional. The avatar URL provided by the third-party login platform
Avatar string
// optional. The original user information provided by the third-party login platform
MetaInfo string
// optional. The bio provided by the third-party login platform
Bio string
}
// ExternalLoginOAuthState stores the local OAuth request state.
type ExternalLoginOAuthState struct {
Provider string `json:"provider"`
Intent string `json:"intent"`
UserID string `json:"user_id,omitempty"`
}
// ExternalLoginUnbindingReq external login unbinding user
type ExternalLoginUnbindingReq struct {
ExternalID string `validate:"required,gt=0,lte=128" json:"external_id"`
UserID string `json:"-"`
}
// UserCenterUserSettingsResp user center user info response
type UserCenterUserSettingsResp struct {
ProfileSettingAgent UserSettingAgent `json:"profile_setting_agent"`
AccountSettingAgent UserSettingAgent `json:"account_setting_agent"`
}
type UserCenterAdminFunctionAgentResp struct {
AllowCreateUser bool `json:"allow_create_user"`
AllowUpdateUserStatus bool `json:"allow_update_user_status"`
AllowUpdateUserPassword bool `json:"allow_update_user_password"`
AllowUpdateUserRole bool `json:"allow_update_user_role"`
}
type UserSettingAgent struct {
Enabled bool `json:"enabled"`
RedirectURL string `json:"redirect_url"`
}
+101
View File
@@ -0,0 +1,101 @@
/*
* 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 (
"encoding/json"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
)
type NotificationChannelConfig struct {
Key constant.NotificationChannelKey `json:"key"`
Enable bool `json:"enable"`
}
type NotificationChannels []*NotificationChannelConfig
func NewNotificationChannelsFormJson(jsonStr string) NotificationChannels {
var list NotificationChannels
_ = json.Unmarshal([]byte(jsonStr), &list)
return list
}
func NewNotificationChannelConfigFormJson(jsonStr string) NotificationChannelConfig {
var list NotificationChannels
_ = json.Unmarshal([]byte(jsonStr), &list)
if len(list) > 0 {
return *list[0]
}
return NotificationChannelConfig{}
}
func (n *NotificationChannels) ToJsonString() string {
data, _ := json.Marshal(n)
return string(data)
}
type NotificationConfig struct {
Inbox NotificationChannelConfig `json:"inbox"`
AllNewQuestion NotificationChannelConfig `json:"all_new_question"`
AllNewQuestionForFollowingTags NotificationChannelConfig `json:"all_new_question_for_following_tags"`
}
func NewNotificationConfig(configs []*entity.UserNotificationConfig) NotificationConfig {
nc := NotificationConfig{}
for _, item := range configs {
switch item.Source {
case string(constant.InboxSource):
nc.Inbox = NewNotificationChannelConfigFormJson(item.Channels)
case string(constant.AllNewQuestionSource):
nc.AllNewQuestion = NewNotificationChannelConfigFormJson(item.Channels)
case string(constant.AllNewQuestionForFollowingTagsSource):
nc.AllNewQuestionForFollowingTags = NewNotificationChannelConfigFormJson(item.Channels)
}
}
return nc
}
func (n *NotificationConfig) Format() {
if n.Inbox.Key == "" {
n.Inbox.Key = constant.EmailChannel
n.Inbox.Enable = false
}
if n.AllNewQuestion.Key == "" {
n.AllNewQuestion.Key = constant.EmailChannel
n.AllNewQuestion.Enable = false
}
if n.AllNewQuestionForFollowingTags.Key == "" {
n.AllNewQuestionForFollowingTags.Key = constant.EmailChannel
n.AllNewQuestionForFollowingTags.Enable = false
}
}
// UpdateUserNotificationConfigReq update user notification config request
type UpdateUserNotificationConfigReq struct {
NotificationConfig
UserID string `json:"-"`
}
// GetUserNotificationConfigResp get user notification config response
type GetUserNotificationConfigResp struct {
NotificationConfig
}
+453
View File
@@ -0,0 +1,453 @@
/*
* 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"
"encoding/json"
"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/pkg/day"
"github.com/segmentfault/pacman/errors"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/pkg/checker"
"github.com/apache/answer/pkg/converter"
"github.com/jinzhu/copier"
)
// UserVerifyEmailReq user verify email request
type UserVerifyEmailReq struct {
// code
Code string `validate:"required,gt=0,lte=500" form:"code"`
// content
Content string `json:"-"`
}
// UserLoginResp get user response
type UserLoginResp struct {
// user id
ID string `json:"id"`
// create time
CreatedAt int64 `json:"created_at"`
// last login date
LastLoginDate int64 `json:"last_login_date"`
// username
Username string `json:"username"`
// email
EMail string `json:"e_mail"`
// mail status(1 pass 2 to be verified)
MailStatus int `json:"mail_status"`
// notice status(1 on 2off)
NoticeStatus int `json:"notice_status"`
// follow count
FollowCount int `json:"follow_count"`
// answer count
AnswerCount int `json:"answer_count"`
// question count
QuestionCount int `json:"question_count"`
// rank
Rank int `json:"rank"`
// authority group
AuthorityGroup int `json:"authority_group"`
// display name
DisplayName string `json:"display_name"`
// avatar
Avatar string `json:"avatar"`
// mobile
Mobile string `json:"mobile"`
// bio markdown
Bio string `json:"bio"`
// bio html
BioHTML string `json:"bio_html"`
// website
Website string `json:"website"`
// location
Location string `json:"location"`
// language
Language string `json:"language"`
// Color scheme
ColorScheme string `json:"color_scheme"`
// access token
AccessToken string `json:"access_token"`
// role id
RoleID int `json:"role_id"`
// user status
Status string `json:"status"`
// user have password
HavePassword bool `json:"have_password"`
// visit token
VisitToken string `json:"visit_token"`
// suspended until timestamp
SuspendedUntil int64 `json:"suspended_until"`
}
func (r *UserLoginResp) ConvertFromUserEntity(userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
r.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
r.HavePassword = len(userInfo.Pass) > 0
if !userInfo.SuspendedUntil.IsZero() {
r.SuspendedUntil = userInfo.SuspendedUntil.Unix()
}
}
type GetCurrentLoginUserInfoResp struct {
*UserLoginResp
Avatar *AvatarInfo `json:"avatar"`
}
func (r *GetCurrentLoginUserInfoResp) ConvertFromUserEntity(userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
r.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
if len(r.ColorScheme) == 0 {
r.ColorScheme = constant.ColorSchemeDefault
}
if !userInfo.SuspendedUntil.IsZero() {
r.SuspendedUntil = userInfo.SuspendedUntil.Unix()
}
}
// GetOtherUserInfoByUsernameResp get user response
type GetOtherUserInfoByUsernameResp struct {
// user id
ID string `json:"id"`
// create time
CreatedAt int64 `json:"created_at"`
// last login date
LastLoginDate int64 `json:"last_login_date"`
// username
Username string `json:"username"`
// email
// follow count
FollowCount int `json:"follow_count"`
// answer count
AnswerCount int `json:"answer_count"`
// question count
QuestionCount int `json:"question_count"`
// rank
Rank int `json:"rank"`
// display name
DisplayName string `json:"display_name"`
// avatar
Avatar string `json:"avatar"`
// mobile
Mobile string `json:"mobile"`
// bio markdown
Bio string `json:"bio"`
// bio html
BioHTML string `json:"bio_html"`
// website
Website string `json:"website"`
// location
Location string `json:"location"`
Status string `json:"status"`
StatusMsg string `json:"status_msg,omitempty"`
// suspended until timestamp
SuspendedUntil int64 `json:"suspended_until"`
}
func (r *GetOtherUserInfoByUsernameResp) ConvertFromUserEntity(userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
r.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
if !userInfo.SuspendedUntil.IsZero() {
r.SuspendedUntil = userInfo.SuspendedUntil.Unix()
}
r.StatusMsg = ""
}
func (r *GetOtherUserInfoByUsernameResp) ConvertFromUserEntityWithLang(ctx context.Context, userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
r.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
lang := handler.GetLangByCtx(ctx)
if userInfo.MailStatus == entity.EmailStatusToBeVerified {
r.StatusMsg = translator.Tr(lang, reason.UserStatusInactive)
}
switch userInfo.Status {
case entity.UserStatusSuspended:
if userInfo.SuspendedUntil.IsZero() || userInfo.SuspendedUntil.Year() >= 2099 {
r.StatusMsg = translator.Tr(lang, reason.UserStatusSuspendedForever)
} else {
r.SuspendedUntil = userInfo.SuspendedUntil.Unix()
trans := translator.GlobalTrans.Tr(lang, "ui.dates.long_date_with_time")
suspendedUntilFormatted := day.Format(userInfo.SuspendedUntil.Unix(), trans, "UTC")
r.StatusMsg = translator.TrWithData(lang, reason.UserStatusSuspendedUntil, map[string]any{
"SuspendedUntil": suspendedUntilFormatted,
})
}
case entity.UserStatusDeleted:
r.StatusMsg = translator.Tr(lang, reason.UserStatusDeleted)
}
}
// UserEmailLoginReq user email login request
type UserEmailLoginReq struct {
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail"`
Pass string `validate:"required,gte=8,lte=32" json:"pass"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
// UserRegisterReq user register request
type UserRegisterReq struct {
Name string `validate:"required,gte=2,lte=30" json:"name"`
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" `
Pass string `validate:"required,gte=8,lte=32" json:"pass"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
IP string `json:"-" `
RequireEmailVerification bool `json:"-"`
}
func (u *UserRegisterReq) Check() (errFields []*validator.FormErrorField, err error) {
if err = checker.CheckPassword(u.Pass); err != nil {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "pass",
ErrorMsg: err.Error(),
})
return errFields, err
}
return nil, nil
}
type UserModifyPasswordReq struct {
OldPass string `validate:"omitempty,gte=8,lte=32" json:"old_pass"`
Pass string `validate:"required,gte=8,lte=32" json:"pass"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
UserID string `json:"-"`
AccessToken string `json:"-"`
}
func (u *UserModifyPasswordReq) Check() (errFields []*validator.FormErrorField, err error) {
if err = checker.CheckPassword(u.Pass); err != nil {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "pass",
ErrorMsg: err.Error(),
})
return errFields, err
}
return nil, nil
}
type UpdateInfoRequest struct {
DisplayName string `validate:"omitempty,gte=2,lte=30" json:"display_name"`
Username string `validate:"omitempty,gte=2,lte=30" json:"username"`
Avatar AvatarInfo `json:"avatar"`
Bio string `validate:"omitempty,gt=0,lte=4096" json:"bio"`
BioHTML string `json:"-"`
Website string `validate:"omitempty,gt=0,lte=500" json:"website"`
Location string `validate:"omitempty,gt=0,lte=100" json:"location"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
type AvatarInfo struct {
Type string `validate:"omitempty,gt=0,lte=100" json:"type"`
Gravatar string `validate:"omitempty,gt=0,lte=200" json:"gravatar"`
Custom string `validate:"omitempty,gt=0,lte=200" json:"custom"`
}
func (a *AvatarInfo) ToJsonString() string {
data, _ := json.Marshal(a)
return string(data)
}
func (a *AvatarInfo) GetURL() string {
switch a.Type {
case constant.AvatarTypeGravatar:
return a.Gravatar
case constant.AvatarTypeCustom:
return a.Custom
default:
return ""
}
}
func CustomAvatar(url string) *AvatarInfo {
return &AvatarInfo{
Type: constant.AvatarTypeCustom,
Custom: url,
}
}
func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
req.BioHTML = converter.Markdown2BasicHTML(req.Bio)
if len(req.Website) > 0 && !checker.IsURL(req.Website) {
return append(errFields, &validator.FormErrorField{
ErrorField: "website",
ErrorMsg: reason.InvalidURLError,
}), errors.BadRequest(reason.InvalidURLError)
}
return nil, nil
}
// UpdateUserInterfaceRequest update user interface request
type UpdateUserInterfaceRequest struct {
// language
Language string `validate:"required,gt=1,lte=100" json:"language"`
// Color scheme
ColorScheme string `validate:"required,gt=1,lte=100" json:"color_scheme"`
// user id
UserId string `json:"-"`
}
func (req *UpdateUserInterfaceRequest) Check() (errFields []*validator.FormErrorField, err error) {
if !translator.CheckLanguageIsValid(req.Language) {
return nil, errors.BadRequest(reason.LangNotFound)
}
if req.ColorScheme != constant.ColorSchemeDefault &&
req.ColorScheme != constant.ColorSchemeLight &&
req.ColorScheme != constant.ColorSchemeDark &&
req.ColorScheme != constant.ColorSchemeSystem {
req.ColorScheme = constant.ColorSchemeDefault
}
return nil, nil
}
type UserRetrievePassWordRequest struct {
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
type UserRePassWordRequest struct {
Code string `validate:"required,gt=0,lte=100" json:"code"`
Pass string `validate:"required,gt=0,lte=32" json:"pass"`
Content string `json:"-"`
}
func (u *UserRePassWordRequest) Check() (errFields []*validator.FormErrorField, err error) {
if err = checker.CheckPassword(u.Pass); err != nil {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "pass",
ErrorMsg: err.Error(),
})
return errFields, err
}
return nil, nil
}
type ActionRecordReq struct {
Action string `validate:"required,oneof=email password edit_userinfo question answer comment edit invitation_answer search report delete vote" form:"action"`
IP string `json:"-"`
UserID string `json:"-"`
}
type ActionRecordResp struct {
CaptchaID string `json:"captcha_id"`
CaptchaImg string `json:"captcha_img"`
Verify bool `json:"verify"`
}
type UserBasicInfo struct {
ID string `json:"id"`
Username string `json:"username"`
Rank int `json:"rank"`
DisplayName string `json:"display_name"`
Avatar string `json:"avatar"`
Website string `json:"website"`
Location string `json:"location"`
Language string `json:"language"`
Status string `json:"status"`
SuspendedUntil int64 `json:"suspended_until"`
}
type GetOtherUserInfoByUsernameReq struct {
Username string `validate:"required,gt=0,lte=500" form:"username"`
UserID string `json:"-"`
IsAdmin bool `json:"-"`
}
type GetOtherUserInfoResp struct {
Info *GetOtherUserInfoByUsernameResp `json:"info"`
}
type UserChangeEmailSendCodeReq struct {
UserVerifyEmailSendReq
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail"`
Pass string `validate:"omitempty,gte=8,lte=32" json:"pass"`
UserID string `json:"-"`
}
type UserChangeEmailVerifyReq struct {
Code string `validate:"required,gt=0,lte=500" json:"code"`
Content string `json:"-"`
}
type UserVerifyEmailSendReq struct {
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
// UserRankingResp user ranking response
type UserRankingResp struct {
UsersWithTheMostReputation []*UserRankingSimpleInfo `json:"users_with_the_most_reputation"`
UsersWithTheMostVote []*UserRankingSimpleInfo `json:"users_with_the_most_vote"`
Staffs []*UserRankingSimpleInfo `json:"staffs"`
}
// UserRankingSimpleInfo user ranking simple info
type UserRankingSimpleInfo struct {
// username
Username string `json:"username"`
// rank
Rank int `json:"rank"`
// vote
VoteCount int `json:"vote_count"`
// display name
DisplayName string `json:"display_name"`
// avatar
Avatar string `json:"avatar"`
}
// UserUnsubscribeNotificationReq user unsubscribe email notification request
type UserUnsubscribeNotificationReq struct {
Code string `validate:"required,gt=0,lte=500" json:"code"`
Content string `json:"-"`
}
// GetUserStaffReq get user staff request
type GetUserStaffReq struct {
Username string `validate:"omitempty,gt=0,lte=500" form:"username"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
}
// GetUserStaffResp get user staff response
type GetUserStaffResp struct {
// username
Username string `json:"username"`
// display name
DisplayName string `json:"display_name"`
// avatar
Avatar string `json:"avatar"`
}
+98
View File
@@ -0,0 +1,98 @@
/*
* 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
type VoteReq struct {
ObjectID string `validate:"required" json:"object_id"`
IsCancel bool `validate:"omitempty" json:"is_cancel"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
UserID string `json:"-"`
}
type VoteResp struct {
UpVotes int64 `json:"up_votes"`
DownVotes int64 `json:"down_votes"`
Votes int64 `json:"votes"`
VoteStatus string `json:"vote_status"`
}
// VoteOperationInfo vote operation info
type VoteOperationInfo struct {
// operation object id
ObjectID string
// question answer comment
ObjectType string
// object owner user id
ObjectCreatorUserID string
// operation user id
OperatingUserID string
// vote up
VoteUp bool
// vote down
VoteDown bool
// vote activity info
Activities []*VoteActivity
}
// VoteActivity vote activity
type VoteActivity struct {
ActivityType int
ActivityUserID string
TriggerUserID string
Rank int
}
func (v *VoteActivity) HasRank() int {
if v.Rank != 0 {
return 1
}
return 0
}
type GetVoteWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// user id
UserID string `json:"-"`
}
type GetVoteWithPageResp struct {
// create time
CreatedAt int64 `json:"created_at"`
// object id
ObjectID string `json:"object_id"`
// question id
QuestionID string `json:"question_id"`
// answer id
AnswerID string `json:"answer_id"`
// object type
ObjectType string `json:"object_type" enums:"question,answer,tag,comment"`
// title
Title string `json:"title"`
// url title
UrlTitle string `json:"url_title"`
// content
Content string `json:"content"`
// vote type
VoteType string `json:"vote_type"`
}