Skip to content

Commit 1247a5d

Browse files
committed
init
1 parent 6e2dfff commit 1247a5d

File tree

6 files changed

+156
-62
lines changed

6 files changed

+156
-62
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/
2+
.DS_Store/

backend/api.go

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
package main
22

33
import (
4-
"github.com/gofiber/fiber"
5-
"github.com/opentreehole/go-common"
4+
"github.com/gin-gonic/gin"
65
"gorm.io/gorm"
6+
"net/http"
7+
"slices"
8+
"strconv"
79
)
810

9-
func ListFloorsInAHole(c *fiber.Ctx) error {
10-
// validate
11-
holeID, err := c.ParamsInt("id")
11+
func ListFloorsInASpecialHole(c *gin.Context) {
12+
var err error
13+
idString := c.Param("holeID")
14+
holeID, err := strconv.Atoi(idString)
1215
if err != nil {
13-
return err
16+
c.String(http.StatusBadRequest, "invalid hole holeID: %s", idString)
17+
return
18+
}
19+
if !slices.Contains(Config.SpecialHoleIDs, holeID) {
20+
c.String(http.StatusForbidden, "hole %d is not a special hole", holeID)
21+
return
1422
}
1523

1624
var query ListModel
17-
err = common.ValidateQuery(c, &query)
18-
if err != nil {
19-
return err
25+
if err := c.ShouldBindQuery(&query); err != nil {
26+
c.String(http.StatusBadRequest, "invalid query: %v", err)
27+
return
2028
}
21-
2229
// get floors
2330
var floors Floors
2431
// use ranking field to locate faster
25-
querySet, err := floors.MakeQuerySet(&holeID, &query.Offset, &query.Size, c)
32+
querySet, err := floors.MakeQuerySet(&holeID, &query.Offset, &query.Size)
2633
if err != nil {
27-
return err
34+
c.String(http.StatusInternalServerError, "failed to make query set: %v", err)
35+
return
2836
}
2937
result := querySet.Order(query.OrderBy + " " + query.Sort).
3038
Find(&floors)
3139
if result.Error != nil {
32-
return result.Error
40+
c.String(http.StatusInternalServerError, "failed to query floors: %v", result.Error)
41+
return
3342
}
3443

35-
return floors.Preprocess(c)
44+
c.JSON(http.StatusOK, floors)
3645
}
3746

38-
func (floors Floors) MakeQuerySet(holeID *int, offset, size *int, c *fiber.Ctx) (*gorm.DB, error) {
47+
func (floors Floors) MakeQuerySet(holeID *int, offset, size *int) (*gorm.DB, error) {
48+
var querySet *gorm.DB
3949
if holeID != nil {
4050
querySet = DB.Preload("Mention").Where("hole_id = ?", holeID)
4151
}
@@ -48,30 +58,3 @@ func (floors Floors) MakeQuerySet(holeID *int, offset, size *int, c *fiber.Ctx)
4858
}
4959
return querySet, nil
5060
}
51-
52-
func (floors Floors) Preprocess(c *fiber.Ctx) (err error) {
53-
userID, err := common.GetUserID(c)
54-
if err != nil {
55-
return
56-
}
57-
58-
// get floors' like
59-
err = floors.loadFloorLikes(c)
60-
if err != nil {
61-
return
62-
}
63-
64-
// set floors IsMe
65-
for _, floor := range floors {
66-
floor.IsMe = userID == floor.UserID
67-
}
68-
69-
// set some default values
70-
for _, floor := range floors {
71-
err = floor.SetDefaults(c)
72-
if err != nil {
73-
return
74-
}
75-
}
76-
return
77-
}

backend/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module main
33
go 1.23.0
44

55
require (
6+
github.com/caarlos0/env/v9 v9.0.0
67
github.com/gin-gonic/gin v1.10.0
78
gorm.io/driver/mysql v1.5.7
89
gorm.io/gorm v1.25.12
@@ -20,6 +21,7 @@ require (
2021
github.com/go-playground/validator/v10 v10.20.0 // indirect
2122
github.com/go-sql-driver/mysql v1.7.0 // indirect
2223
github.com/goccy/go-json v0.10.2 // indirect
24+
github.com/google/go-cmp v0.5.8 // indirect
2325
github.com/jinzhu/inflection v1.0.0 // indirect
2426
github.com/jinzhu/now v1.1.5 // indirect
2527
github.com/json-iterator/go v1.1.12 // indirect

backend/go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc
22
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
33
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
44
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
5+
github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
6+
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
57
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
68
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
79
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@@ -27,8 +29,8 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
2729
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
2830
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
2931
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
30-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
31-
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
32+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
33+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3234
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
3335
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
3436
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@@ -82,8 +84,6 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
8284
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
8385
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
8486
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
85-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
86-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
8787
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
8888
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
8989
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

backend/main.go

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,79 @@
11
package main
22

33
import (
4-
"log"
5-
"net/http"
6-
"os"
7-
4+
"context"
5+
"github.com/caarlos0/env/v9"
86
"github.com/gin-gonic/gin"
97
"gorm.io/driver/mysql"
108
"gorm.io/gorm"
9+
"log"
10+
"net/http"
11+
"os"
12+
"os/signal"
13+
"syscall"
14+
"time"
1115
)
1216

13-
func main() {
14-
dsn := os.Getenv("CONFIG")
15-
if dsn == "" {
16-
log.Fatal("CONFIG environment variable is not set")
17+
var DB *gorm.DB
18+
19+
var Config struct {
20+
DbURL string `env:"DB_URL"`
21+
SpecialHoleIDs []int `env:"SPECIAL_HOLE_IDS"`
22+
}
23+
24+
func Init() (app *gin.Engine) {
25+
var err error
26+
if err := env.Parse(&Config); err != nil {
27+
log.Fatal("failed to parse env: %v", err)
1728
}
29+
log.Println("Config: ", Config)
30+
31+
DB, err = gorm.Open(mysql.Open(Config.DbURL), &gorm.Config{})
1832

19-
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
2033
if err != nil {
2134
log.Fatalf("cannot connect to database: %v", err)
35+
return nil
2236
}
2337

24-
if err := db.AutoMigrate(&User{}); err != nil {
25-
log.Fatalf("automigrate failed: %v", err)
38+
app = gin.Default()
39+
40+
// app.Get("/floors/:id<int>/special", GetSpecialFloor)
41+
// Define a route with an integer ID parameter and an empty path (might be a subgroup)
42+
app.GET("/floors/:id/special", ListFloorsInASpecialHole)
43+
return app
44+
}
45+
46+
func main() {
47+
app := Init()
48+
49+
// Create a custom HTTP server with Gin
50+
srv := &http.Server{
51+
Addr: ":8080",
52+
Handler: app,
2653
}
2754

28-
r := gin.Default()
55+
// Run server in a goroutine
56+
go func() {
57+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
58+
log.Fatalf("Server error: %v", err)
59+
}
60+
}()
2961

30-
// app.Get("/floors/:id<int>/special", GetSpecialFloor)
31-
r.Get("")
62+
// Wait for interrupt signal (CTRL+C / SIGTERM)
63+
quit := make(chan os.Signal, 1)
64+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
65+
<-quit
66+
67+
log.Println("Shutting down server...")
68+
69+
// Create a context with a 5-second timeout
70+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
71+
defer cancel()
72+
73+
// Gracefully shut down the server
74+
if err := srv.Shutdown(ctx); err != nil {
75+
log.Fatalf("Server forced to shutdown: %v", err)
76+
}
3277

33-
r.Run(":8080")
78+
log.Println("Server exited gracefully")
3479
}

backend/model.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,70 @@
11
package main
22

3+
import (
4+
"time"
5+
)
6+
37
type ListModel struct {
48
Size int `json:"size" query:"size" default:"30" validate:"min=0,max=50"` // length of object array
59
Offset int `json:"offset" query:"offset" default:"0" validate:"min=0"` // offset of object array
610
Sort string `json:"sort" query:"sort" default:"asc" validate:"oneof=asc desc"` // Sort order
711
OrderBy string `json:"order_by" query:"order_by" default:"id" validate:"oneof=id like"` // SQL ORDER BY field
8-
}
12+
}
13+
type Floor struct {
14+
/// saved fields
15+
ID int `json:"id"`
16+
CreatedAt time.Time `json:"time_created"`
17+
UpdatedAt time.Time `json:"time_updated"`
18+
19+
/// base info
20+
21+
// content of the floor, no more than 15000, should be sensitive checked, no more than 10000 in frontend
22+
Content string `json:"content"`
23+
24+
// a random username
25+
Anonyname string `json:"anonyname"`
26+
27+
// the ranking of this floor in the hole
28+
Ranking int `json:"ranking"`
29+
30+
// floor_id that it replies to, for dialog mode, in the same hole
31+
ReplyTo int `json:"reply_to"`
32+
33+
// like number
34+
Like int `json:"like"`
35+
36+
// dislike number
37+
Dislike int `json:"dislike"`
38+
39+
// whether the floor is deleted
40+
Deleted bool `json:"deleted"`
41+
42+
// the modification times of floor.content
43+
Modified int `json:"modified"`
44+
45+
// fold reason
46+
Fold string `json:"fold_v2"`
47+
48+
// additional info, like "树洞管理团队"
49+
SpecialTag string `json:"special_tag"`
50+
51+
// the user who wrote it
52+
UserID int `json:"-"`
53+
54+
// the hole it belongs to
55+
HoleID int `json:"hole_id"`
56+
57+
// many to many mentions
58+
Mention Floors `json:"mention"`
59+
}
60+
61+
type Floors []*Floor
62+
63+
type FloorMention struct {
64+
FloorID int `json:"floor_id"`
65+
MentionID int `json:"mention_id"`
66+
}
67+
68+
func (FloorMention) TableName() string {
69+
return "floor_mention"
70+
}

0 commit comments

Comments
 (0)