mirror of https://github.com/casbin/casnode.git
feat: sync gitter room to node (#519)
* fix: update count field not atomic * test: add sync topic count field test * chore: remove redundant select queries * feat: sync gitter to node * feat: add go test & debug gitter.go * debug * feat: configure gitter room in fe * debug * chore: gitter sync limit time and frequence * format * debug * debug & fix test
This commit is contained in:
parent
ab5466e0b2
commit
6f97edf5b2
3
go.mod
3
go.mod
|
@ -19,8 +19,11 @@ require (
|
|||
github.com/mileusna/crontab v1.0.1
|
||||
github.com/mozillazg/go-slugify v0.2.0
|
||||
github.com/mozillazg/go-unidecode v0.1.1 // indirect
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
||||
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
|
|
6
go.sum
6
go.sum
|
@ -244,6 +244,10 @@ github.com/mozillazg/go-slugify v0.2.0 h1:SIhqDlnJWZH8OdiTmQgeXR28AOnypmAXPeOTcG
|
|||
github.com/mozillazg/go-slugify v0.2.0/go.mod h1:z7dPH74PZf2ZPFkyxx+zjPD8CNzRJNa1CGacv0gg8Ns=
|
||||
github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE=
|
||||
github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d h1:tLWCMSjfL8XyZwpu1RzI2UpJSPbZCOZ6DVHQFnlpL7A=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff/go.mod h1:B8jLfIIPn2sKyWr0D7cL2v7tnrDD5z291s2Zypdu89E=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
|
@ -298,6 +302,8 @@ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z
|
|||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6 h1:7AV47xvYbuwoNxR9LDhkRwqzZsySCX5H8WVM4zrDmME=
|
||||
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6/go.mod h1:P2BoF5QlNE1UcKtYKP8xa8B9I5eALYU5JpRdCqLddL4=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
|
@ -40,8 +40,11 @@ func InitForumBasicInfo() {
|
|||
if AutoSyncPeriodSecond >= 30 {
|
||||
fmt.Println("Auto sync from google group enabled.")
|
||||
go AutoSyncGoogleGroup()
|
||||
fmt.Println("Auto sync from gitter room enabled.")
|
||||
go AutoSyncGitter()
|
||||
} else {
|
||||
fmt.Println("Auto sync from google group disabled.")
|
||||
fmt.Println("Auto sync from gitter room disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed 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 object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/casbin/casnode/util"
|
||||
"github.com/casdoor/casdoor-go-sdk/auth"
|
||||
"github.com/sromku/go-gitter"
|
||||
)
|
||||
|
||||
const (
|
||||
topicDuration = "4" // Hours
|
||||
apiLIMIT = 10 // request frequency
|
||||
)
|
||||
|
||||
type topicGitter struct {
|
||||
Topic Topic
|
||||
Massages []gitter.Message
|
||||
MemberMsgMap map[string]int
|
||||
}
|
||||
|
||||
var (
|
||||
roomSyncMsgHeadMap = map[string]string{}
|
||||
roomSyncMsgTailMap = map[string]string{}
|
||||
lastMsgMap = map[string]gitter.Message{}
|
||||
lastTopicMap = map[string]topicGitter{}
|
||||
currentTopicMap = map[string]topicGitter{}
|
||||
)
|
||||
|
||||
func AutoSyncGitter() {
|
||||
if AutoSyncPeriodSecond < 30 {
|
||||
return
|
||||
}
|
||||
for {
|
||||
time.Sleep(time.Duration(AutoSyncPeriodSecond) * time.Second)
|
||||
SyncAllGitterRooms()
|
||||
}
|
||||
//SyncAllGitterRooms()
|
||||
}
|
||||
|
||||
func SyncAllGitterRooms() {
|
||||
fmt.Println("Sync from gitter room started...")
|
||||
var nodes []Node
|
||||
err := adapter.Engine.Find(&nodes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, node := range nodes {
|
||||
node.SyncGitter()
|
||||
}
|
||||
}
|
||||
|
||||
func (n Node) SyncGitter() {
|
||||
if n.GitterRoomURL == "" || n.GitterApiToken == "" {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
handleErr(err.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// Get your own token At https://developer.gitter.im/
|
||||
api := gitter.New(n.GitterApiToken)
|
||||
|
||||
// get room id by room url
|
||||
rooms, err := api.GetRooms()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("gitter room urls:", n.GitterRoomURL)
|
||||
|
||||
room := gitter.Room{}
|
||||
for _, v := range rooms { // find RoomId by url
|
||||
if "https://gitter.im/"+v.URI == n.GitterRoomURL {
|
||||
room = v
|
||||
break
|
||||
}
|
||||
}
|
||||
if room.Name == "" {
|
||||
panic(errors.New("room is not exist"))
|
||||
}
|
||||
|
||||
topics := n.GetAllTopicsByNode()
|
||||
topicNum := len(topics)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := []gitter.Message{}
|
||||
|
||||
// get sync index, it is the last sync message id
|
||||
headIdx, ok := roomSyncMsgHeadMap[room.ID]
|
||||
if !ok { // get all msg if idx is not exist
|
||||
for _, topic := range topics {
|
||||
if topic.GitterMessageId != "" {
|
||||
// get reply
|
||||
replies := GetRepliesOfTopic(topic.Id)
|
||||
// get sync msg idx
|
||||
num := len(replies)
|
||||
if num == 0 {
|
||||
headIdx = topic.GitterMessageId
|
||||
break
|
||||
}
|
||||
|
||||
flag := false
|
||||
for i := num - 1; i >= 0; i-- {
|
||||
if replies[i].GitterMessageId != "" {
|
||||
headIdx = replies[i].GitterMessageId
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the api limits the number of messages to 50
|
||||
messages, err = api.GetMessages(room.ID, &gitter.Pagination{
|
||||
AfterID: headIdx,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(messages) == 0 {
|
||||
roomSyncMsgHeadMap[room.ID] = headIdx
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < apiLIMIT; i++ { // restrict request frequency
|
||||
msgs, err := api.GetMessages(room.ID, &gitter.Pagination{
|
||||
AfterID: messages[len(messages)-1].ID,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
break
|
||||
}
|
||||
messages = append(messages, msgs...)
|
||||
}
|
||||
|
||||
fmt.Printf("sync msg for room(msgNum:%d): %s\n", len(messages), room.Name)
|
||||
createTopicWithMessages(messages, room, n, topics, true)
|
||||
}()
|
||||
|
||||
// tail
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
messages := []gitter.Message{}
|
||||
|
||||
tailIdx, ok := roomSyncMsgTailMap[room.ID]
|
||||
if !ok {
|
||||
for i := topicNum - 1; i >= 0; i-- {
|
||||
topic := topics[i]
|
||||
if topic.GitterMessageId != "" {
|
||||
tailIdx = topic.GitterMessageId
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Time{}
|
||||
tExist := true // if t is not exist, sync all msg
|
||||
if n.GitterSyncFromTime == "" {
|
||||
tExist = false
|
||||
} else {
|
||||
t, err = time.Parse(time.RFC3339, n.GitterSyncFromTime)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if tailIdx != "" {
|
||||
tailMsg, err := api.GetMessage(room.ID, tailIdx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if tExist {
|
||||
if tailMsg.Sent.Before(t) { // if msg is before the start time, end sync tail
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messages, err = api.GetMessages(room.ID, nil)
|
||||
if len(messages) != 0 {
|
||||
tailIdx = messages[0].ID
|
||||
}
|
||||
}
|
||||
|
||||
messages, err = api.GetMessages(room.ID, &gitter.Pagination{
|
||||
BeforeID: tailIdx,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(messages) == 0 {
|
||||
roomSyncMsgTailMap[room.ID] = tailIdx
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < apiLIMIT; i++ { // restrict request frequency
|
||||
msgs, err := api.GetMessages(room.ID, &gitter.Pagination{
|
||||
BeforeID: messages[0].ID,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
num := len(msgs)
|
||||
if num == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// if msg is before the start time, end sync tail
|
||||
if tExist {
|
||||
if msgs[0].Sent.Before(t) {
|
||||
for i := num - 1; i > 0; i-- {
|
||||
if msgs[i].Sent.Before(t) {
|
||||
if i == num-1 {
|
||||
msgs = []gitter.Message{}
|
||||
} else {
|
||||
msgs = msgs[i+1:]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
messages = append(msgs, messages...)
|
||||
break
|
||||
}
|
||||
}
|
||||
messages = append(msgs, messages...)
|
||||
}
|
||||
|
||||
fmt.Printf("sync msg for room(msgNum:%d): %s\n", len(messages), room.Name)
|
||||
createTopicWithMessages(messages, room, n, topics, false)
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// main create topic func
|
||||
func createTopicWithMessages(messages []gitter.Message, room gitter.Room, node Node, topics []Topic, asc bool) {
|
||||
GetTopicExist := func(topicTitle string) Topic {
|
||||
for _, topic := range topics {
|
||||
if topic.Title == topicTitle {
|
||||
return topic
|
||||
}
|
||||
}
|
||||
return Topic{}
|
||||
}
|
||||
|
||||
// initialize value
|
||||
lastMsg, ok := lastMsgMap[room.ID]
|
||||
if !ok {
|
||||
lastMsg = gitter.Message{}
|
||||
}
|
||||
lastTopic := lastTopicMap[room.ID]
|
||||
if !ok {
|
||||
lastTopic = topicGitter{MemberMsgMap: map[string]int{}}
|
||||
}
|
||||
currentTopic, ok := currentTopicMap[room.ID]
|
||||
if !ok {
|
||||
currentTopic = topicGitter{MemberMsgMap: map[string]int{}}
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
handleErr(err.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// create if user is not exist
|
||||
user, err := auth.GetUser(msg.From.Username)
|
||||
//fmt.Println("user:", user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if user.Id == "" { // add user
|
||||
newUser := auth.User{
|
||||
Name: msg.From.Username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
UpdatedTime: util.GetCurrentTime(),
|
||||
DisplayName: msg.From.DisplayName,
|
||||
Avatar: msg.From.AvatarURLMedium,
|
||||
SignupApplication: CasdoorApplication,
|
||||
}
|
||||
fmt.Println("add user: ", newUser.Name)
|
||||
_, err := auth.AddUser(&newUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var mentioned = false // if @user
|
||||
for _, mention := range msg.Mentions {
|
||||
if mention.ScreenName == lastMsg.From.Username {
|
||||
mentioned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if @user and lastMsg is not @user, then create topic
|
||||
// if duration is more than 4 hour, then create topic
|
||||
|
||||
d := msg.Sent.Sub(lastMsg.Sent)
|
||||
dur, err := strconv.Atoi(topicDuration)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if d > time.Hour*time.Duration(dur) && !mentioned { // if dur > `TopicDuration` and not @user last replied
|
||||
tmpStr := []rune(msg.Text)
|
||||
if len(tmpStr) > 200 { // limit length
|
||||
tmpStr = tmpStr[:200]
|
||||
}
|
||||
title := string(tmpStr)
|
||||
|
||||
topic := GetTopicExist(title)
|
||||
if topic.Id == 0 { // not exist
|
||||
// add topic
|
||||
topic = Topic{
|
||||
Author: msg.From.Username,
|
||||
NodeId: node.Id,
|
||||
NodeName: node.Name,
|
||||
TabId: node.TabId,
|
||||
Title: title,
|
||||
CreatedTime: msg.Sent.String(),
|
||||
Content: msg.Text,
|
||||
IsHidden: true,
|
||||
GitterMessageId: msg.ID,
|
||||
}
|
||||
|
||||
_, topicID := AddTopic(&topic)
|
||||
topic.Id = topicID
|
||||
}
|
||||
|
||||
// deep copy
|
||||
data, _ := json.Marshal(currentTopic)
|
||||
_ = json.Unmarshal(data, &lastTopic)
|
||||
lastTopicMap[room.ID] = lastTopic
|
||||
|
||||
// new currentTopic
|
||||
currentTopic = topicGitter{Topic: topic, MemberMsgMap: map[string]int{}}
|
||||
currentTopic.Massages = append(currentTopic.Massages, msg)
|
||||
currentTopic.MemberMsgMap[msg.From.Username]++
|
||||
currentTopicMap[room.ID] = currentTopic
|
||||
} else {
|
||||
// add reply to lastTopic
|
||||
reply := Reply{
|
||||
Author: msg.From.Username,
|
||||
TopicId: currentTopic.Topic.Id,
|
||||
CreatedTime: msg.Sent.String(),
|
||||
Content: msg.Text,
|
||||
GitterMessageId: msg.ID,
|
||||
}
|
||||
_, _ = AddReply(&reply)
|
||||
|
||||
ChangeTopicReplyCount(reply.TopicId, 1)
|
||||
ChangeTopicLastReplyUser(currentTopic.Topic.Id, msg.From.Username, msg.Sent.String())
|
||||
|
||||
currentTopic.Massages = append(currentTopic.Massages, msg)
|
||||
currentTopic.MemberMsgMap[msg.From.Username]++
|
||||
currentTopicMap[room.ID] = currentTopic
|
||||
}
|
||||
|
||||
// deep copy
|
||||
data, _ := json.Marshal(msg)
|
||||
_ = json.Unmarshal(data, &lastMsg)
|
||||
|
||||
// add index to sync message
|
||||
if asc {
|
||||
roomSyncMsgHeadMap[room.ID] = msg.ID
|
||||
} else {
|
||||
roomSyncMsgTailMap[room.ID] = messages[0].ID
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func handleErr(err error) {
|
||||
var stack string
|
||||
logs.Critical("Handler crashed with error:", err)
|
||||
for i := 1; ; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2022 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/issue9/assert"
|
||||
"github.com/sromku/go-gitter"
|
||||
)
|
||||
|
||||
func TestRemoveSyncGitterData(t *testing.T) {
|
||||
InitConfig()
|
||||
InitAdapter()
|
||||
|
||||
// delete all sync gitter data
|
||||
var nodes []Node
|
||||
err := adapter.Engine.Find(&nodes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.GitterRoomURL == "" || node.GitterApiToken == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
api := gitter.New(node.GitterApiToken)
|
||||
rooms, err := api.GetRooms()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
url := node.GitterRoomURL
|
||||
room := gitter.Room{}
|
||||
for _, v := range rooms { // find RoomId by url
|
||||
if "https://gitter.im/"+v.URI == url {
|
||||
room = v
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotEqual(t, room.Name, "")
|
||||
adapter.Engine.ShowSQL(true)
|
||||
_, err = adapter.Engine.
|
||||
Query("DELETE t.*,r.* FROM topic as t LEFT JOIN reply as r ON t.id = r.topic_id WHERE t.gitter_message_id is not null AND t.node_id = ?", node.Id)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("INFO: delete sync gitter data of room: %s\n", room.Name)
|
||||
}
|
||||
}
|
|
@ -21,26 +21,29 @@ import (
|
|||
)
|
||||
|
||||
type Node struct {
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Name string `xorm:"varchar(100)" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||
Desc string `xorm:"mediumtext" json:"desc"`
|
||||
Extra string `xorm:"mediumtext" json:"extra"`
|
||||
Image string `xorm:"varchar(200)" json:"image"`
|
||||
BackgroundImage string `xorm:"varchar(200)" json:"backgroundImage"`
|
||||
HeaderImage string `xorm:"varchar(200)" json:"headerImage"`
|
||||
BackgroundColor string `xorm:"varchar(20)" json:"backgroundColor"`
|
||||
BackgroundRepeat string `xorm:"varchar(20)" json:"backgroundRepeat"`
|
||||
TabId string `xorm:"varchar(100)" json:"tab"`
|
||||
ParentNode string `xorm:"varchar(200)" json:"parentNode"`
|
||||
PlaneId string `xorm:"varchar(50)" json:"planeId"`
|
||||
Sorter int `json:"sorter"`
|
||||
Ranking int `json:"ranking"`
|
||||
Hot int `json:"hot"`
|
||||
Moderators []string `xorm:"varchar(200)" json:"moderators"`
|
||||
MailingList string `xorm:"varchar(100)" json:"mailingList"`
|
||||
GoogleGroupCookie string `xorm:"varchar(1500)" json:"googleGroupCookie"`
|
||||
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Name string `xorm:"varchar(100)" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||
Desc string `xorm:"mediumtext" json:"desc"`
|
||||
Extra string `xorm:"mediumtext" json:"extra"`
|
||||
Image string `xorm:"varchar(200)" json:"image"`
|
||||
BackgroundImage string `xorm:"varchar(200)" json:"backgroundImage"`
|
||||
HeaderImage string `xorm:"varchar(200)" json:"headerImage"`
|
||||
BackgroundColor string `xorm:"varchar(20)" json:"backgroundColor"`
|
||||
BackgroundRepeat string `xorm:"varchar(20)" json:"backgroundRepeat"`
|
||||
TabId string `xorm:"varchar(100)" json:"tab"`
|
||||
ParentNode string `xorm:"varchar(200)" json:"parentNode"`
|
||||
PlaneId string `xorm:"varchar(50)" json:"planeId"`
|
||||
Sorter int `json:"sorter"`
|
||||
Ranking int `json:"ranking"`
|
||||
Hot int `json:"hot"`
|
||||
Moderators []string `xorm:"varchar(200)" json:"moderators"`
|
||||
MailingList string `xorm:"varchar(100)" json:"mailingList"`
|
||||
GoogleGroupCookie string `xorm:"varchar(1500)" json:"googleGroupCookie"`
|
||||
GitterApiToken string `xorm:"varchar(200)" json:"gitterApiToken"`
|
||||
GitterRoomURL string `xorm:"varchar(200)" json:"gitterRoomUrl"`
|
||||
GitterSyncFromTime string `xorm:"varchar(40)" json:"gitterSyncFromTime"`
|
||||
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||
}
|
||||
|
||||
func GetNodes() []*Node {
|
||||
|
@ -343,3 +346,12 @@ func (n Node) GetAllTopicTitlesOfNode() []string {
|
|||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (n Node) GetAllTopicsByNode() []Topic {
|
||||
var topics []Topic
|
||||
err := adapter.Engine.Where("node_id = ? and deleted = 0", n.Id).Desc("created_time").Find(&topics)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return topics
|
||||
}
|
||||
|
|
|
@ -23,19 +23,20 @@ import (
|
|||
)
|
||||
|
||||
type Reply struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Author string `xorm:"varchar(100) index" json:"author"`
|
||||
TopicId int `xorm:"int index" json:"topicId"`
|
||||
ParentId int `xorm:"int" json:"parentId"`
|
||||
Tags []string `xorm:"varchar(200)" json:"tags"`
|
||||
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||
Deleted bool `xorm:"bool" json:"deleted"`
|
||||
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||
ThanksNum int `xorm:"int" json:"thanksNum"`
|
||||
EditorType string `xorm:"varchar(40)" json:"editorType"`
|
||||
Content string `xorm:"mediumtext" json:"content"`
|
||||
Ip string `xorm:"varchar(100)" json:"ip"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Author string `xorm:"varchar(100) index" json:"author"`
|
||||
TopicId int `xorm:"int index" json:"topicId"`
|
||||
ParentId int `xorm:"int" json:"parentId"`
|
||||
Tags []string `xorm:"varchar(200)" json:"tags"`
|
||||
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||
Deleted bool `xorm:"bool" json:"deleted"`
|
||||
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||
ThanksNum int `xorm:"int" json:"thanksNum"`
|
||||
EditorType string `xorm:"varchar(40)" json:"editorType"`
|
||||
Content string `xorm:"mediumtext" json:"content"`
|
||||
Ip string `xorm:"varchar(100)" json:"ip"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
GitterMessageId string `xorm:"varchar(100)" json:"gitterMessageId"`
|
||||
}
|
||||
|
||||
var enableNestedReply, _ = beego.AppConfig.Bool("enableNestedReply")
|
||||
|
|
|
@ -53,6 +53,7 @@ type Topic struct {
|
|||
IsHidden bool `xorm:"bool index" json:"isHidden"`
|
||||
Ip string `xorm:"varchar(100)" json:"ip"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
GitterMessageId string `xorm:"varchar(100)" json:"gitterMessageId"`
|
||||
}
|
||||
|
||||
func GetTopicCount() int {
|
||||
|
|
|
@ -187,6 +187,8 @@ class AdminNode extends React.Component {
|
|||
form["headerImage"] = this.state.nodeInfo?.headerImage;
|
||||
form["mailingList"] = this.state.nodeInfo?.mailingList;
|
||||
form["googleGroupCookie"] = this.state.nodeInfo?.googleGroupCookie;
|
||||
form["gitterApiToken"] = this.state.nodeInfo?.gitterApiToken;
|
||||
form["gitterRoomUrl"] = this.state.nodeInfo?.gitterRoomUrl;
|
||||
form["isHidden"] = this.state.nodeInfo?.isHidden;
|
||||
}
|
||||
|
||||
|
@ -718,6 +720,22 @@ class AdminNode extends React.Component {
|
|||
<input type="text" className="sl" name="googleGroupCookie" defaultValue={this.state.form?.googleGroupCookie} onChange={(event) => this.updateFormField("googleGroupCookie", event.target.value)} autoComplete="off" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" align="right">
|
||||
{i18next.t("node:Gitter API Token")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input type="text" className="sl" name="gitterApiToken" defaultValue={this.state.form?.gitterApiToken} onChange={(event) => this.updateFormField("googleGroupCookie", event.target.value)} autoComplete="off" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" align="right">
|
||||
{i18next.t("node:Gitter Room URL")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input type="text" className="sl" name="gitterRoomUrl" defaultValue={this.state.form?.gitterRoomUrl} onChange={(event) => this.updateFormField("googleGroupCookie", event.target.value)} autoComplete="off" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" align="right">
|
||||
{i18next.t("node:Is hidden")}
|
||||
|
|
Loading…
Reference in New Issue