This commit is contained in:
jiayuqi7813
2022-05-21 23:29:23 +08:00
parent b8859af6d8
commit 7cb171bc90
83 changed files with 1687 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
main.go

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="snctf.db" uuid="1f1ae82f-c0e0-4fcb-a9c5-2156507a34a7">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/snctf.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/snctf.iml" filepath="$PROJECT_DIR$/.idea/snctf.iml" />
</modules>
</component>
</project>

9
.idea/snctf.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

38
api/admin/auth.go Normal file
View File

@@ -0,0 +1,38 @@
package admin
import (
"github.com/gin-gonic/gin"
."main.go/type"
"main.go/api"
)
// AuthRequired 用于管理员权限控制的中间件
func AuthRequired()gin.HandlerFunc {
return func(c *gin.Context) {
session, err := api.Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get SNCTFSESSID error"})
c.Abort()
return
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(200, gin.H{"code": 400, "msg": "No session"})
c.Abort()
return
}
if user.Role != 1 {
c.JSON(200, gin.H{"code": 400, "msg": "Unauthorized access!"})
c.Abort()
return
}
c.Next()
}
}

79
api/admin/challenge.go Normal file
View File

@@ -0,0 +1,79 @@
package admin
import (
"fmt"
"github.com/gin-gonic/gin"
"main.go/api"
db "main.go/database"
. "main.go/type"
"strings"
)
// CrDb 专门用作给数据替换。
type CrDb struct {
Name string `json:"name" binding:"required"`
Score int `json:"score" binding:"required"`
Flag string `json:"flag"` // 暂时一个题只能一个flag
Description string `json:"description"`
Attachment string `json:"attachment"`
Category string `json:"category" binding:"required"`
Tags string `json:"tags"`
Hints string `json:"hints"`
Visible int `json:"visible"`
}
//NewChallenge 新建一个题目
func NewChallenge(c *gin.Context){
var request ChallengeRequest
if err := c.ShouldBindJSON(&request); err != nil {
fmt.Println(err)
c.JSON(400, gin.H{"code": 400, "msg": "Request format wrong!"})
return
}
challenge := &Challenge{
Name: request.Name,
Score: request.Score,
Flag: request.Flag,
Description: request.Description,
Attachment: request.Attachment,
Category: request.Category,
Tags: request.Tags,
Hints: request.Hints,
Visible: request.Visible,
}
if err := addChallenge(challenge); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Add challenge failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "msg": "Add challenge success!"})
}
//addChallenge 添加题目内容
func addChallenge(c *Challenge) error {
api.Link()
DB := db.DBsnctf
// 使用逗号分隔字符串
attachmentString := strings.Join(c.Attachment, ",")
hintString := strings.Join(c.Hints, ",")
crdb := &CrDb{
Name: c.Name,
Score: c.Score,
Flag: c.Flag,
Description: c.Description,
Attachment: attachmentString,
Category: c.Category,
Tags: c.Tags,
Hints: hintString,
Visible: c.Visible,
}
//插入数据
err := DB.Table("challenge").Create(crdb).Error
//command := "INSERT INTO challenge (name,score,flag,description,attachment,category,tags,hints,visible) VALUES (?,?,?,?,?,?,?,?,?);"
//err := DB.Debug().Raw(command,c.Name, c.Score, c.Flag, c.Description, attachmentString, c.Category, c.Tags, hintString, c.Visible).Error
if err != nil {
return err
}
return nil
}

58
api/category.go Normal file
View File

@@ -0,0 +1,58 @@
package api
import (
"github.com/gin-gonic/gin"
db "main.go/database"
)
// GetCategories 获取所有题目分类。
func GetCategories(c *gin.Context) {
var categories []string
if err := getAllCategories(&categories); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get categories failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": categories})
}
//getAllCategories 操作数据库所有题目分类
func getAllCategories(categories *[]string) error {
Link()
DB := db.DBsnctf
rows,err:= DB.Raw("select category from category").Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var category string
if err := rows.Scan(&category); err != nil {
return err
}
*categories = append(*categories, category)
}
return rows.Err()
}
// CheckCategory 检查类别是否正确
func CheckCategory(c string) bool {
var categories []string
if err := getAllCategories(&categories); err != nil {
return false
}
for _, category := range categories {
if category == c {
return true
}
}
return false
}

84
api/challenge.go Normal file
View File

@@ -0,0 +1,84 @@
package api
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
db "main.go/database"
. "main.go/type"
"strings"
)
//GetAllChallenges 获取全部题目
func GetAllChallenges(c * gin.Context){
var challenges []ChallengeResponse
if err := getAllChallenges(c, &challenges); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get all challenges failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": challenges})
}
//GetChallengesByCategory 获取某个分类下的题目
func GetChallengesByCategory(c *gin.Context){
category := c.Param("category")
if matched := CheckCategory(category); !matched {
c.JSON(400, gin.H{"code": 400, "msg": "Wrong category!"})
return
}
var challenges []ChallengeResponse
if err := getAllChallenges(c, &challenges); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get all challenges failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": challenges})
}
// getAllChallenges 操作数据库获取所有题目。
func getAllChallenges(c *gin.Context, challenges *[]ChallengeResponse) error {
var attachmentString, hints string
Link()
DB := db.DBsnctf
rows,err := DB.Raw("SELECT id, name, score, description, attachment, category, tags, hints FROM challenge WHERE visible=1;").Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next(){
var challenge ChallengeResponse
err = rows.Scan(&challenge.ID, &challenge.Name, &challenge.Score, &challenge.Description, &attachmentString, &challenge.Category, &challenge.Tags, &hints)
fmt.Println(err)
if err != nil {
return err
}
// 解析为切片
challenge.Attachment = strings.Split(attachmentString, ",")
challenge.Hints = strings.Split(hints, ",")
solverCount, err := getSolverCount(challenge.ID)
if err != nil {
return err
}
challenge.SolverCount = solverCount
session, err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get SNCTFSESSID error"})
return err
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(200, gin.H{"code": 400, "msg": "No session"})
return errors.New("no session")
}
challenge.IsSolved = hasAlreadySolved(user.ID, challenge.ID)
*challenges = append(*challenges, challenge)
}
return rows.Err()
}

39
api/notice.go Normal file
View File

@@ -0,0 +1,39 @@
package api
import (
"github.com/gin-gonic/gin"
. "main.go/type"
db "main.go/database"
)
// GetAllNotices 获取所有的公告
func GetAllNotices(c *gin.Context) {
var notices []Notice
if err := getAllNotices(&notices); err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get all notices failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": notices})
}
func getAllNotices(notices *[]Notice) error {
Link()
DB := db.DBsnctf
command := "SELECT id, title, content, created_at FROM notice ORDER BY created_at ASC;"
rows, err := DB.Raw(command).Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var b Notice
err = rows.Scan(&b.ID, &b.Title, &b.Content, &b.CreatedAt)
if err != nil {
return err
}
*notices = append(*notices, b)
}
return rows.Err()
}

78
api/score.go Normal file
View File

@@ -0,0 +1,78 @@
package api
import (
"github.com/gin-gonic/gin"
db "main.go/database"
"main.go/tools"
. "main.go/type"
)
//GetAllScore 获取所有的积分,按照积分降序排列
func GetAllScore(c *gin.Context){
var s ScoreResponse
var scores []ScoreResponse
Link()
DB :=db.DBsnctf
//rows,err := DB.Raw("SELECT s.id, s.username, s.score FROM score AS s, user AS u WHERE u.hidden = 0 AND s.username = u.username ORDER BY s.score DESC;").Scan(&s).Rows()
rows,err := DB.Debug().Raw("SELECT s.id, s.username, s.score FROM score AS s, user AS u WHERE u.hidden = 0 AND s.username = u.username ORDER BY s.score DESC;").Rows()
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get all score error!"})
return
}
// 循环读取数据
for rows.Next() {
rows.Scan(&s.ID, &s.Username, &s.Score)
scores = append(scores, s)
}
c.JSON(200, gin.H{"code": 200, "data": scores})
}
//GetScoreByUserId 获取用户分数
func GetScoreByUserId(c *gin.Context) {
var score int
Link()
DB := db.DBsnctf
//获取用户id
id := c.Params.ByName("id")
if id == "" {
c.JSON(400, gin.H{"code": 400, "msg": "Need id!"})
return
}
//检查id是否合法
if !tools.CheckID(id) {
c.JSON(400, gin.H{"code": 400, "msg": "ID format error!"})
return
}
//查询用户信息
err := DB.Raw("SELECT s.score FROM score AS s, user AS u WHERE u.id = ? AND u.hidden = 0 AND u.username = s.username LIMIT 1;", id).Scan(&score).Error
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get info error!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": score})
}
//GetSelfScoreAndRank 获取当前登录用户的分数和排名
func GetSelfScoreAndRank(c *gin.Context){
var scoreAndRank ScoreRankResponse
DB := db.DBsnctf
session, err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get SNCTFSESSID error"})
return
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(200, gin.H{"code": 400, "msg": "No session"})
return
}
err = DB.Raw("SELECT score, (SELECT count(DISTINCT score) FROM score WHERE score>=s.score) AS rank FROM score AS s,user AS u WHERE u.id = ? AND u.username = s.username ORDER BY score DESC LIMIT 1;",user.ID).Scan(&scoreAndRank).Error
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get info error!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": scoreAndRank})
}

27
api/session.go Normal file
View File

@@ -0,0 +1,27 @@
package api
import (
"encoding/gob"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
. "main.go/type"
)
// sessions 存储于文件系统
var Store *sessions.FilesystemStore
func init() {
Store = sessions.NewFilesystemStore("./sessions", securecookie.GenerateRandomKey(32))
Store.Options = &sessions.Options{
Domain: "",
Path: "/",
MaxAge: 24 * 60 * 60, // 1 day
// SameSite: http.SameSiteNoneMode,
Secure: false,
HttpOnly: false,
}
gob.Register(User{})
}

153
api/solve.go Normal file
View File

@@ -0,0 +1,153 @@
package api
import (
"github.com/gin-gonic/gin"
db "main.go/database"
."main.go/type"
"strconv"
)
//GetAllSolves 获取所有解题记录
func GetAllSolves(c *gin.Context){
var solves []SolveResponse
if err := getAllSolves(&solves); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get all solves failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": solves})
}
//GetSolvesByCid 获取某个用户的所有解题记录
func GetSolvesByCid(c *gin.Context){
var solves []SolveResponse
cid, err := strconv.ParseInt(c.Param("cid"), 10, 64)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get solves failure!"})
return
}
if err := getSolvesByCid(&solves, int(cid)); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get solves failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": solves})
}
// GetSolvesByUid 根据用户id获取正确的flag提交记录。
func GetSolvesByUid(c *gin.Context) {
uid, err := strconv.ParseInt(c.Param("uid"), 10, 64)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Wrong uid!"})
return
}
if uid == 1 {
c.JSON(400, gin.H{"code": 400, "msg": "Not allowed!"})
return
}
var solves []SolveResponse
if err := getSolvesByUid(&solves, int(uid)); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get specified solves failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": solves})
}
// GetSelfSolves 获取当前用户的所有解题记录
func GetSelfSolves(c *gin.Context){
var solves []SolveResponse
session, err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": "Get SNCTFSESSID error"})
return
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(200, gin.H{"code": 400, "msg": "No session"})
return
}
if err := getSolvesByUid(&solves, user.ID); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get self solves failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": solves})
}
// getSolverCount 操作数据库获取指定id题目的解出人数。
func getSolverCount(id int) (count int, err error) {
Link()
DB := db.DBsnctf
//SELECT COUNT(*) FROM solve WHERE cid = ?;
err = DB.Table("solve").Select("COUNT(*)").Where("cid = ?", id).Scan(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
// getAllSolves 操作数据库获取所有正确的提交记录,按提交时间从早到晚排序。
func getAllSolves(solves *[]SolveResponse) error {
DB :=db.DBsnctf
rows,err := DB.Raw("SELECT s.id, s.uid, s.cid, u.username, c.name, s.submitted_at, c.score FROM solve AS s, user AS u, challenge AS c WHERE u.hidden=0 AND s.uid=u.id AND s.cid=c.id ORDER BY s.submitted_at ASC;").Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var s SolveResponse
err = rows.Scan(&s.ID, &s.Uid, &s.Cid, &s.Username, &s.ChallengeName, &s.SubmittedAt, &s.Score)
if err != nil {
return err
}
*solves = append(*solves, s)
}
return rows.Err()
}
// getSolvesByUid 操作数据库根据用户id获取正确的flag提交记录按提交时间从早到晚排序。
func getSolvesByUid(solves *[]SolveResponse, uid int) error {
DB :=db.DBsnctf
rows,err := DB.Debug().Raw("SELECT s.id, s.uid, s.cid, u.username, c.name, s.submitted_at, c.score FROM solve AS s, user AS u, challenge AS c WHERE u.hidden=0 AND s.uid=? AND u.id=s.uid AND c.id=s.cid ORDER BY s.submitted_at ASC;",uid).Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var s SolveResponse
err = rows.Scan(&s.ID, &s.Uid, &s.Cid, &s.Username, &s.ChallengeName, &s.SubmittedAt, &s.Score)
if err != nil {
return err
}
*solves = append(*solves, s)
}
return rows.Err()
}
// getSolvesByCid 操作数据库根据题目id获取正确的提交记录按提交时间从早到晚排序。
func getSolvesByCid(solves *[]SolveResponse, cid int) error {
DB :=db.DBsnctf
rows,err := DB.Raw("SELECT s.id, s.uid, s.cid, u.username, c.name, s.submitted_at, c.score FROM solve AS s, user AS u, challenge AS c WHERE u.hidden=0 AND s.cid=? AND u.id=s.uid AND c.id=s.cid ORDER BY s.submitted_at ASC;",cid).Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var s SolveResponse
err = rows.Scan(&s.ID, &s.Uid, &s.Cid, &s.Username, &s.ChallengeName, &s.SubmittedAt, &s.Score)
if err != nil {
return err
}
*solves = append(*solves, s)
}
return rows.Err()
}

211
api/submissions.go Normal file
View File

@@ -0,0 +1,211 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
db "main.go/database"
"main.go/tools"
. "main.go/type"
"math"
)
// hasAlreadySolved 检查某道题是否已经被某用户解出。
func hasAlreadySolved(uid int, cid int) (exists bool) {
Link()
DB := db.DBsnctf
err := DB.Raw("SELECT EXISTS(SELECT 1 FROM solve WHERE uid=? AND cid=?)",uid,cid).Scan(&exists).Error
if err != nil {
return false
}
return exists
}
// isChallengeExisted 检查数据库中是否存在某个题目。
func isChallengeExisted(id int) (exists bool) {
Link()
DB := db.DBsnctf
command := "SELECT EXISTS(SELECT 1 FROM challenge WHERE id = ?);"
err := DB.Raw(command,id).Scan(&exists).Error
if err != nil {
return false
}
return exists
}
//addSubmission 添加一个提交记录。
func addSubmission(s *Submission) error {
Link()
DB := db.DBsnctf
err := DB.Table("submission").Create(&s).Error
if err != nil {
return err
}
return nil
}
// getFlag 根据题目id获取该题的flag
func getFlag(id int) (flag string, err error) {
Link()
DB := db.DBsnctf
err = DB.Table("challenge").Select("flag").Where("id = ?",id).Find(&flag).Error
if err != nil {
return "", err
}
return flag, nil
}
// addSolve 操作数据库加入一条正确的flag提交记录。
func addSolve(s *Solve) error {
DB := db.DBsnctf
err := DB.Table("solve").Create(&s).Error
if err != nil {
return err
}
return nil
}
// addUserScore 操作数据库为指定用户增加某题的分数。
func addUserScore(username string, cid int) error {
var newScore int
DB := db.DBsnctf
err := DB.Table("challenge").Select("score").Where("id = ?", cid).Find(&newScore).Error
if err != nil {
return err
}
command := "UPDATE score SET score=score+? WHERE username=?"
err = DB.Exec(command, newScore, username).Error
if err != nil {
return err
}
return nil
}
// updateUserScores 操作数据库更新解出用户的分数。
func updateUserScores(reducedScore, cid int) error {
DB:=db.DBsnctf
command := "UPDATE score SET score=score-? WHERE EXISTS(SELECT 1 FROM user,solve WHERE user.id=solve.uid AND score.username=user.username AND solve.cid=?);"
err := DB.Exec(command, reducedScore, cid).Error
return err
}
// editChallengeScore 操作数据库修改指定题目增的动态分数。
func editChallengeScore(cid int) (reducedScore int, err error) {
DB := db.DBsnctf
var currentScore int
err = DB.Table("challenge").Select("score").Where("id = ?", cid).Find(&currentScore).Error
command := "SELECT score FROM challenge WHERE id=? LIMIT 1;"
if err != nil {
return 0, err
}
solverCount, err := getSolverCount(cid)
if err != nil {
return 0, err
}
// According to https://github.com/o-o-overflow/scoring-playground
newScore := int(100 + (1000-100)/(1.0+float64(solverCount)*0.04*math.Log(float64(solverCount))))
reducedScore = currentScore - newScore
command = "UPDATE challenge SET score=? WHERE id=?;"
err = DB.Exec(command, newScore, cid).Error
if err != nil {
return 0, err
}
return reducedScore, nil
}
// SubmitFlag 提交flag
func SubmitFlag(c *gin.Context) {
var request SubmissionRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Request format wrong!"})
return
}
session,err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "GET SNCTFSESSID error!"})
return
}
user,ok := session.Values["user"].(User)
if !ok {
c.JSON(400, gin.H{"code": 400, "msg": "GET user error!"})
return
}
// 检查题目是否存在
if !isChallengeExisted(request.Cid) {
c.JSON(400, gin.H{"code": 400, "msg": "Challenge not existed!"})
return
}
// Submission记录
solvedTime := tools.Timestamp()
submission := &Submission{
UserID: user.ID,
ChallengeID: request.Cid,
Flag: request.Flag,
IP: c.ClientIP(),
Time: solvedTime,
}
err = addSubmission(submission)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Record submission failure!"})
return
}
// 是否已经解出该题
if hasAlreadySolved(user.ID, request.Cid) {
c.JSON(400, gin.H{"code": 400, "msg": "Already solved!"})
return
}
// 获取flag
flag, err := getFlag(request.Cid)
if err != nil {
fmt.Println(err)
c.JSON(400, gin.H{"code": 400, "msg": "Get flag failure!"})
return
}
// 检查flag是否正确
if flag != request.Flag {
c.JSON(400, gin.H{"code": 400, "msg": "Wrong flag!"})
return
}else {
// Solve记录
solve := &Solve{
UserID: user.ID,
ChallengeID: request.Cid,
Time: solvedTime,
}
err = addSolve(solve)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Record solve failure!"})
return
}
//加分
err = addUserScore(user.Username, request.Cid)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Add score failure!"})
return
}
// 题目动态分数
reducedScore, err := editChallengeScore(request.Cid)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Edit challenge score failure!"})
return
}
//更新所有用户分数
err = updateUserScores(reducedScore, request.Cid)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Update user scores failure!"})
return
}
c.JSON(200, gin.H{"code": 200, "msg": "Correct flag!"})
return
}
}

296
api/user.go Normal file
View File

@@ -0,0 +1,296 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
_ "gorm.io/driver/sqlite"
db "main.go/database"
"main.go/tools"
. "main.go/type"
"regexp"
"unicode/utf8"
)
//连接数据库
func Link() {
err := db.Inimysql()
if err != nil{
panic(err)
}
}
func Login(c * gin.Context) {
var request LoginRequest
var user User
Link()
DB := db.DBsnctf
DB.AutoMigrate(&User{})
//用ShouldBindJSON解析绑定传入的Json数据。
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Bind json error!"})
return
}
//code 0用户名不存在
//code 1用户名或密码错误
err := DB.Take(&user,"username = ?",request.Username).Error
if err != nil {
c.JSON(200,gin.H{
"Error":true,
"Msg":"登陆失败",
"Code": 0,
})
return
}
//判断md5值与数据库内容是否相同
if user.Password != tools.MD5(request.Password){
c.JSON(200,gin.H{
"Error":true,
"Msg":"用户名或密码错误",
"Code": 1,
})
return
}
//身份认证结束
//session
// 设置session
session, _ := Store.Get(c.Request, "SNCTFSESSID")
user.Password = ""
// 根据remember值设置session有效期
if request.Remember {
session.Options.MaxAge = 7 * 24 * 60 * 60 // 7 days
} else {
session.Options.MaxAge = 24 * 60 * 60 // 1 day
}
//保存session
session.Values["user"] = user
err = session.Save(c.Request, c.Writer)
fmt.Println(err)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Save SNCTFSESSID error"})
return
}
//登录成功
c.JSON(200, gin.H{"code": 200, "username": user.Username, "role": user.Role, "msg": "Login success!"})
}
func Register(c *gin.Context) {
var request RegisterRequest
var user User
var score ScoreResponse
Link()
DB := db.DBsnctf
err := DB.AutoMigrate(&User{})
if err != nil {
return
}
//用ShouldBindJSON解析绑定传入的Json数据。
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Bind json error!"})
return
}
//限制传入用户名为中文、数字、大小写字母下划线和横杠1到10位
if !checkUsername(request.Username) {
c.JSON(200, gin.H{
"Error": true,
"Msg": "用户名不符合规范",
"Code": 2,
})
return
}
//限制密码长度6到20位
if !checkPassword(request.Password) {
c.JSON(400, gin.H{"code": 400, "msg": "Password format error!"})
return
}
//限制传入邮箱符合格式
if !checkEmail(request.Email) {
c.JSON(400, gin.H{"code": 400, "msg": "Email format error!"})
return
}
//判断用户名是否已被使用
if isNameExisted(user, request.Username) {
c.JSON(200, gin.H{"code": 1000, "msg": "Username has already been used!"})
return
}
//判断邮箱是否已被使用
if isEmailExisted(user, request.Email) {
c.JSON(200, gin.H{"code": 1001, "msg": "Email has already been used!"})
return
}
user.Token = tools.Token()
user.Username = request.Username
user.Password = tools.MD5(request.Password)
user.Email = request.Email
user.Created = tools.Timestamp()
score.Score = 0
score.Username =request.Username
//创建数据
err1 := DB.Table("user").Create(&user).Error
err2 := DB.Table("score").Create(&score).Error
if err1 != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Register error!"})
return
}
if err2 != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Register error!"})
return
}
c.JSON(200, gin.H{
"success": "注册成功",
})
}
// Logout 实现用户注销登陆
func Logout(c *gin.Context) {
var user User
session, err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get CTFGOSESSID error"})
return
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(400, gin.H{"code": 400, "msg": "No session"})
return
}
session.Options.MaxAge = -1
err = session.Save(c.Request, c.Writer)
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Save CTFGOSESSID error"})
return
}
fmt.Sprintf("[%s] logout success!", user.Username)
c.JSON(200, gin.H{"code": 200, "msg": "Logout success!"})
}
//GetInfoByUserId 获取用户信息
func GetInfoByUserId(c *gin.Context) {
var info PublicInfoResponse
Link()
DB := db.DBsnctf
//获取用户id
id := c.Params.ByName("id")
if id == "" {
c.JSON(400, gin.H{"code": 400, "msg": "Need id!"})
return
}
//检查id是否合法
if !tools.CheckID(id) {
c.JSON(400, gin.H{"code": 400, "msg": "ID format error!"})
return
}
err := DB.Debug().Raw("SELECT username,affiliation,country,team_id FROM user WHERE id = ? LIMIT 1", id).Scan(&info).Error
//err := DB.Where("id = ?", id).First(user).Error
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get info error!"})
return
}
c.JSON(200, gin.H{"code": 200, "data": info})
}
// checkUsername 验证用户名是否符合中文数字字母下划线横杠长度1到10位返回true或false
func checkUsername(username string) bool {
if !(utf8.RuneCountInString(username) > 0) || !(utf8.RuneCountInString(username) < 11) {
return false
}
pattern := `^[-\w\p{Han}]+$`
reg := regexp.MustCompile(pattern)
return reg.MatchString(username)
}
// checkEmail 验证是否符合邮箱格式返回true或false
func checkEmail(email string) bool {
pattern := `^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`
reg := regexp.MustCompile(pattern)
return reg.MatchString(email)
}
// checkPassword 验证密码是否符合长度6到20位返回true或false
func checkPassword(password string) bool {
if !(utf8.RuneCountInString(password) > 5) || !(utf8.RuneCountInString(password) < 21) {
return false
}
return true
}
// isNameExisted 判断用户名是否已经被占用被占用返回true未被占用则返回false
func isNameExisted(user User, username string) bool {
Link()
DB := db.DBsnctf
err := DB.First(&user,"Username = ?",username).Error
if err != nil {
return false
}
return true
}
// isNameExisted 判断邮箱是否已经被占用被占用返回true未被占用则返回false
func isEmailExisted(user User, email string) bool {
Link()
DB := db.DBsnctf
err := DB.First(&user,"Email = ?",email).Error
if err != nil {
return false
}
return true
}
// Session 获取当前用户session信息
func Session(c *gin.Context) {
var user User
// 默认在此之前已经通过了中间件的session权限验证
session, _ := Store.Get(c.Request, "SNCTFSESSID")
user = session.Values["user"].(User)
c.JSON(200, gin.H{"code": 200, "data": user})
}
//下面是身份认证用 AUTH部分
// AuthRequired 用于普通用户权限控制的中间件
func AuthRequired()gin.HandlerFunc{
return func(c *gin.Context) {
session, err := Store.Get(c.Request, "SNCTFSESSID")
if err != nil {
c.JSON(400, gin.H{"code": 400, "msg": "Get SNCTFSESSID error"})
c.Abort()
return
}
user, ok := session.Values["user"].(User)
if !ok {
c.JSON(400, gin.H{"code": 400, "msg": "No session"})
c.Abort()
return
}
if user.Role != 0&&user.Role!=1{
c.JSON(400, gin.H{"code": 400, "msg": "Permission denied"})
c.Abort()
return
}
c.Next()
}
}

30
database/db.go Normal file
View File

@@ -0,0 +1,30 @@
package database
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
//定义全局数据库
var (
DBsnctf *gorm.DB
)
//定义全局数据库连接
func Inimysql()(err error){
DBsnctf, err = gorm.Open(sqlite.Open("snctf.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 使用单数表名
},
})
if err != nil {
panic("failed to connect database")
}
return
}
func DBLink() {
err := Inimysql()
if err != nil{
panic(err)
}
}

31
go.mod Normal file
View File

@@ -0,0 +1,31 @@
module main.go
go 1.17
require (
github.com/gin-gonic/gin v1.7.7
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
gorm.io/driver/sqlite v1.3.2
gorm.io/gorm v1.23.5
)
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)

73
go.sum Normal file
View File

@@ -0,0 +1,73 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.3.2 h1:nWTy4cE52K6nnMhv23wLmur9Y3qWbZvOBz+V4PrGAxg=
gorm.io/driver/sqlite v1.3.2/go.mod h1:B+8GyC9K7VgzJAcrcXMRPdnMcck+8FgJynEehEPM16U=
gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

0
identifier.sqlite Normal file
View File

7
main.go Normal file
View File

@@ -0,0 +1,7 @@
package main
import "main.go/routers"
func main() {
routers.Initrouter()
}

72
routers/router.go Normal file
View File

@@ -0,0 +1,72 @@
package routers
import (
"github.com/gin-gonic/gin"
"main.go/api"
"main.go/api/admin"
)
func Initrouter(){
router := gin.Default()
api_v1 := router.Group("/api/v1")
//公共接口(无需登录)
public := api_v1.Group("")
{
//用户登录
public.POST("/login",api.Login)
//用户注册
public.POST("/register",api.Register)
//用户登出
public.GET("/logout",api.Logout)
//获取指定id用户可公开信息
public.GET("/user/:id",api.GetInfoByUserId)
//获取指定id用户分数
public.GET("/score/:id",api.GetScoreByUserId)
//获取所有用户分数,降序排列
public.GET("/score",api.GetAllScore)
//获取全部公告
public.GET("/notice",api.GetAllNotices)
}
// 普通用户api需要用户登陆且Role=0才能访问
personal := api_v1.Group("/user")
personal.Use(api.AuthRequired())
{
// 获取当前用户信息
personal.GET("/session", api.Session)
// 获取题目分类
personal.GET("/category", api.GetCategories)
// 获取所有题目信息
personal.GET("/challenges/all", api.GetAllChallenges)
// 获取指定分类题目信息
personal.GET("/challenges/:category", api.GetChallengesByCategory)
// 提交flag
personal.POST("/submitflag", api.SubmitFlag)
// 获取所有正确的flag提交记录
personal.GET("/solves/all", api.GetAllSolves)
// 获取指定用户正确的flag提交记录
personal.GET("/solves/uid/:uid", api.GetSolvesByUid)
// 获取指定题目正确的flag提交记录
personal.GET("/solves/cid/:cid", api.GetSolvesByCid)
// 获取当前用户正确flag提交记录即解题记录按时间从早到晚排序
personal.GET("/solves/self", api.GetSelfSolves)
// 获取当前用户分数、排名
personal.GET("/score/self", api.GetSelfScoreAndRank)
// 校内排行api暂时留空
}
// 管理员api需要用户登陆且Role=1才能访问
manager := api_v1.Group("/admin")
manager.Use(admin.AuthRequired())
{
// 创建新题目
manager.POST("/challenge", admin.NewChallenge)
}
router.Run(":9000")
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More