Skip to content

Commit c87b0e1

Browse files
committed
Use HTML to display all question content
All question content is stored as HTML within the database and displayed in frontend using dangerouslySetInnerHTML
1 parent df61dad commit c87b0e1

25 files changed

+1701
-169
lines changed

backend/common/logger_struct.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"github.com/sirupsen/logrus"
55
)
66

7-
//contains the logger
7+
// contains the logger
88
type Logger struct {
99
Log *logrus.Logger
1010
}

backend/common/question_struct.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
// defines the JSON format of quesitons.
22
package common
33

4-
type Difficulty int
5-
6-
const (
7-
Easy Difficulty = 1
8-
Medium Difficulty = 2
9-
Hard Difficulty = 3
10-
)
11-
12-
// Question struct
134
type Question struct {
14-
ID int `json:"id"`
15-
Difficulty Difficulty `json:"difficulty"`
16-
Title string `json:"title"`
17-
Description string `json:"description"`
18-
Categories []string `json:"categories"`
19-
TestCases map[string]string `json:"test_cases"`
20-
//Images []string `json:"images"` // for future uses
5+
Title string `json:"title"`
6+
TitleSlug string `json:"titleSlug"`
7+
Difficulty string `json:"difficulty"`
8+
TopicTags []string `json:"topicTags"`
9+
Content string `json:"content"`
10+
Schemas []string `json:"schemas"`
11+
Id int `json:"id"`
2112
}
22-

backend/database/database_interactions.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ package database
44
import (
55
"context"
66
"errors"
7-
"net/http"
87
"fmt"
8+
"net/http"
9+
10+
"peerprep/common"
911

1012
"go.mongodb.org/mongo-driver/bson"
1113
"go.mongodb.org/mongo-driver/mongo/options"
12-
"peerprep/common"
1314
)
1415

15-
func (db *QuestionDB) GetAllQuestionsWithQuery(logger *common.Logger, filter bson.D) ([]common.Question, error) {
16+
func (db *QuestionDB) GetAllQuestionsWithQuery(
17+
logger *common.Logger,
18+
filter bson.D,
19+
) ([]common.Question, error) {
1620
questionCursor, err := db.questions.Find(context.Background(), filter)
1721

1822
if err != nil {
@@ -36,28 +40,23 @@ func (db *QuestionDB) AddQuestion(logger *common.Logger, question *common.Questi
3640
return http.StatusConflict, errors.New("question already exists")
3741
}
3842

39-
question.ID = db.FindNextQuestionId()
40-
41-
if question.ID == -1 {
42-
logger.Log.Error("Could not find next question ID")
43-
return http.StatusBadGateway, errors.New("could not find the next question ID")
44-
}
45-
4643
if _, err := db.questions.InsertOne(context.Background(), question); err != nil {
4744
logger.Log.Error("Error adding question", err.Error())
4845
return http.StatusBadGateway, err
4946
}
5047

51-
db.IncrementNextQuestionId(question.ID + 1, logger)
5248
return http.StatusOK, nil
5349
}
5450

55-
func (db *QuestionDB) UpsertQuestion(logger *common.Logger, question *common.Question) (int, error) {
51+
func (db *QuestionDB) UpsertQuestion(
52+
logger *common.Logger,
53+
question *common.Question,
54+
) (int, error) {
5655

57-
filter := bson.D{bson.E{Key: "id", Value: question.ID}}
56+
filter := bson.D{bson.E{Key: "id", Value: question.Id}}
5857
setter := bson.M{"$set": question}
5958
upsert := options.Update().SetUpsert(true)
60-
59+
6160
_, err := db.questions.UpdateOne(context.Background(), filter, setter, upsert)
6261

6362
if err != nil {
@@ -69,7 +68,10 @@ func (db *QuestionDB) UpsertQuestion(logger *common.Logger, question *common.Que
6968
}
7069

7170
func (db *QuestionDB) DeleteQuestion(logger *common.Logger, id int) (int, error) {
72-
deleteStatus, err := db.questions.DeleteOne(context.Background(), bson.D{bson.E{Key: "id", Value: id}})
71+
deleteStatus, err := db.questions.DeleteOne(
72+
context.Background(),
73+
bson.D{bson.E{Key: "id", Value: id}},
74+
)
7375

7476
if err != nil {
7577
logger.Log.Error("Error deleting question", err.Error())
@@ -81,4 +83,4 @@ func (db *QuestionDB) DeleteQuestion(logger *common.Logger, id int) (int, error)
8183
}
8284

8385
return http.StatusNoContent, nil
84-
}
86+
}

backend/database/database_util.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ package database
33

44
import (
55
"context"
6-
"fmt"
6+
"peerprep/common"
77

88
"go.mongodb.org/mongo-driver/bson"
9-
"peerprep/common"
109
)
1110

1211
// used to check if a question already exists in the database.
1312
func (db *QuestionDB) QuestionExists(question *common.Question) bool {
1413
filter := bson.D{bson.E{Key: "title", Value: question.Title}}
15-
err := db.questions.FindOne(context.Background(), filter).Decode(&common.Question{}) //FindOne() returns error if no document is found
14+
err := db.questions.FindOne(context.Background(), filter).
15+
Decode(&common.Question{})
16+
//FindOne() returns error if no document is found
1617
return err == nil
1718
}
1819

@@ -30,22 +31,15 @@ func (db *QuestionDB) FindNextQuestionId() int {
3031
return id.Next
3132
}
3233

33-
// used to increment the next question ID to be used.
34-
// since the collection is capped at one document, inserting a new document will replace the old one.
35-
func (db *QuestionDB) IncrementNextQuestionId(nextId int, logger *common.Logger) {
36-
var err error
37-
if _, err = db.nextId.InsertOne(context.Background(), bson.D{bson.E{Key: "next", Value: nextId}}); err != nil {
38-
logger.Log.Error("Error incrementing next question ID: ", err)
39-
return
40-
}
41-
42-
logger.Log.Info(fmt.Sprintf("Next question ID incremented to %d successfully", nextId))
43-
}
44-
4534
// used to check if a question being replaced will cause duplicates in the database
4635

4736
func (db *QuestionDB) QuestionExistsExceptId(question *common.Question) bool {
48-
filter := bson.D{bson.E{Key: "title", Value: question.Title}, bson.E{Key: "id", Value: bson.D{bson.E{Key: "$ne", Value: question.ID}}}}
49-
err := db.questions.FindOne(context.Background(), filter).Decode(&common.Question{}) //FindOne() returns error if no document is found
37+
filter := bson.D{
38+
bson.E{Key: "title", Value: question.Title},
39+
bson.E{Key: "id", Value: bson.D{bson.E{Key: "$ne", Value: question.Id}}},
40+
}
41+
err := db.questions.FindOne(context.Background(), filter).
42+
Decode(&common.Question{})
43+
//FindOne() returns error if no document is found
5044
return err == nil
5145
}

backend/database/questionDB_struct.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import (
88
// questions is the collection with all the questions, nextId is a single-entry collection that stores the next ID to be used.
99
type QuestionDB struct {
1010
questions *mongo.Collection
11-
nextId *mongo.Collection
11+
nextId *mongo.Collection
1212
}
1313

1414
// returns a pointer to an instance of the Question collection
1515
func NewQuestionDB(client *mongo.Client) *QuestionDB {
1616

1717
questionCollection := client.Database("questions").Collection("questions")
18-
idCollection := client.Database("questions").Collection("id")
18+
idCollection := client.Database("questions").Collection("counter")
1919
return &QuestionDB{questions: questionCollection, nextId: idCollection}
20-
}
20+
}

backend/go.mod

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module peerprep
22

3-
go 1.20
3+
go 1.23
44

55
require github.com/joho/godotenv v1.5.1 // indirect -allows to load environment variables from a .env file instead of hardcoding them in the code
66

@@ -35,13 +35,18 @@ require (
3535
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
3636
go.mongodb.org/mongo-driver v1.16.1 // indirect
3737
golang.org/x/arch v0.8.0 // indirect
38-
golang.org/x/crypto v0.23.0 // indirect
39-
golang.org/x/net v0.25.0 // indirect
38+
golang.org/x/crypto v0.24.0 // indirect
39+
golang.org/x/net v0.26.0 // indirect
4040
golang.org/x/sync v0.7.0 // indirect
41-
golang.org/x/sys v0.20.0 // indirect
42-
golang.org/x/text v0.15.0 // indirect
41+
golang.org/x/sys v0.21.0 // indirect
42+
golang.org/x/text v0.16.0 // indirect
4343
google.golang.org/protobuf v1.34.1 // indirect
4444
gopkg.in/yaml.v3 v3.0.1 // indirect
4545
)
4646

47-
require github.com/gin-contrib/cors v1.7.2 // indirect
47+
require (
48+
github.com/aymerick/douceur v0.2.0 // indirect
49+
github.com/gin-contrib/cors v1.7.2 // indirect
50+
github.com/gorilla/css v1.0.1 // indirect
51+
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
52+
)

backend/go.sum

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
2+
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
13
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
24
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
35
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
@@ -28,6 +30,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
2830
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
2931
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
3032
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
33+
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
34+
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
3135
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
3236
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
3337
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -42,6 +46,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
4246
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
4347
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
4448
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
49+
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
50+
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
4551
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
4652
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
4753
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -87,12 +93,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
8793
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
8894
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
8995
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
96+
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
97+
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
9098
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
9199
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
92100
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
93101
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
94102
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
95103
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
104+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
105+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
96106
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
97107
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
98108
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
@@ -107,6 +117,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
107117
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
108118
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
109119
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
120+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
121+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
110122
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
111123
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
112124
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -115,6 +127,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
115127
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
116128
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
117129
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
130+
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
118131
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
119132
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
120133
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

backend/main.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,42 @@ package main
33

44
import (
55
"fmt"
6+
"io"
7+
"log"
68
"os"
79
"time"
810

9-
"github.com/gin-gonic/gin"
10-
"github.com/sirupsen/logrus"
11+
"github.com/joho/godotenv"
12+
13+
apicommon "peerprep/common"
1114
apidatabase "peerprep/database"
1215
gintransport "peerprep/transport"
13-
apicommon "peerprep/common"
14-
)
1516

17+
"github.com/gin-gonic/gin"
18+
"github.com/sirupsen/logrus"
19+
)
1620

1721
func main() {
1822
//initialise logger file and directory if they do not exist
23+
24+
err := godotenv.Load("questionDB.env")
25+
if err != nil {
26+
log.Fatal("Error loading environment variables: " + err.Error())
27+
}
28+
29+
ORIGIN := os.Getenv("CORS_ORIGIN")
30+
if ORIGIN == "" {
31+
ORIGIN = "http://localhost:3000"
32+
}
33+
PORT := os.Getenv("PORT")
34+
if PORT == "" {
35+
PORT = ":9090"
36+
}
37+
1938
logger := apicommon.NewLogger(logrus.New())
2039

2140
logDirectory := "./log"
22-
41+
2342
if err := os.MkdirAll(logDirectory, 0755); err != nil {
2443
logger.Log.Error("Failed to create log directory: " + err.Error())
2544
}
@@ -44,12 +63,14 @@ func main() {
4463
//create a new instance of the questionDB
4564
questionDB := apidatabase.NewQuestionDB(server)
4665

47-
66+
f, _ := os.Create("log/gin.log")
67+
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
68+
4869
router := gin.Default()
49-
gintransport.SetCors(router)
70+
gintransport.SetCors(router, ORIGIN)
5071
gintransport.SetAllEndpoints(router, questionDB, logger)
5172

5273
logger.Log.Info(fmt.Sprintf("Server started at time: %s", time.Now().String()))
5374

54-
router.Run(":9090")
75+
router.Run(PORT)
5576
}

backend/transport/add_question.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"peerprep/common"
77
"peerprep/database"
88

9+
"github.com/microcosm-cc/bluemonday"
10+
911
"github.com/gin-gonic/gin"
1012
)
1113

@@ -19,6 +21,9 @@ func AddQuestionWithLogger(db *database.QuestionDB, logger *common.Logger) gin.H
1921
return
2022
}
2123

24+
p := bluemonday.UGCPolicy()
25+
question.Content = p.Sanitize(question.Content)
26+
2227
status, err := db.AddQuestion(logger, &question)
2328

2429
if err != nil {

backend/transport/get_question.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,22 @@ import (
1313
"go.mongodb.org/mongo-driver/bson"
1414
)
1515

16-
1716
func GetQuestionWithLogger(db *database.QuestionDB, logger *common.Logger) gin.HandlerFunc {
1817
return func(ctx *gin.Context) {
1918
// get the numeric ID from the URL
2019
id, err := strconv.Atoi(ctx.Param("id"))
21-
20+
2221
if err != nil {
2322
ctx.JSON(http.StatusBadRequest, gin.H{"Error retrieving question": "Invalid ID"})
2423
logger.Log.Warn("Invalid ID: ", ctx.Param("id"))
2524
return
2625
}
2726

28-
2927
var questions []common.Question
30-
questions, err = db.GetAllQuestionsWithQuery(logger, bson.D{bson.E{Key: "id", Value: id}})
28+
questions, err = db.GetAllQuestionsWithQuery(
29+
logger,
30+
bson.D{bson.E{Key: "id", Value: id}},
31+
)
3132

3233
if err != nil {
3334
ctx.JSON(http.StatusBadGateway, err.Error())
@@ -49,4 +50,4 @@ func GetQuestionWithLogger(db *database.QuestionDB, logger *common.Logger) gin.H
4950
ctx.JSON(http.StatusOK, questions[0])
5051
logger.Log.Info(fmt.Sprintf("Question with ID %d returned successfully", id))
5152
}
52-
}
53+
}

0 commit comments

Comments
 (0)