Skip to content

Commit

Permalink
add: support permanent db (json)
Browse files Browse the repository at this point in the history
  • Loading branch information
holenet committed Aug 28, 2024
1 parent ec7703d commit 9c8a830
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 86 deletions.
1 change: 1 addition & 0 deletions LDLServer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
db.json
61 changes: 61 additions & 0 deletions LDLServer/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"encoding/json"
"io"
"log"
"os"
)

var dbFilePath string = "db.json"

func loadTopics() map[uint64]*Topic {
file, err := os.Open(dbFilePath)
if err != nil {
log.Fatalf("Invalid DB File Path: %s\n", dbFilePath)
}
defer file.Close()

bytes, _ := io.ReadAll(file)
var topics []Topic
err = json.Unmarshal(bytes, &topics)
if err != nil {
log.Fatalf("DB Decoding Failed.")
}

topicMap := make(map[uint64]*Topic, 0)
for _, topic := range topics {
topicMap[topic.Id] = &topic
}
log.Println("Topics Loaded.")

return topicMap
}

func saveTopics(topics map[uint64]*Topic) {
file, err := os.Create(dbFilePath)
if err != nil {
log.Printf("Invalid DB File Path: %s\n", dbFilePath)
return
}
defer file.Close()

topicList := make([]Topic, 0)
for _, topic := range topics {
topicList = append(topicList, *topic)
}

bytes, err := json.Marshal(topicList)
if err != nil {
log.Printf("DB Encoding Failed. %v", err)
return
}

_, err = file.Write(bytes)
if err != nil {
log.Printf("DB Write Failed. %v\n", err)
return
}

log.Println("Topics Saved.")
}
126 changes: 40 additions & 86 deletions LDLServer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,7 @@ func serveHome(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL)
}

type Topic struct {
Id uint64
Content string
Choices []string
Votes []uint64
Deleted bool
}
type TopicForm struct {
Content string
Choices []string
}

func (t Topic) isValidVoteIndex(index uint64) bool {
return int(index) < len(t.Votes)
}
func (t *Topic) makeVotesJson() []byte {
bytes, _ := json.Marshal(t.Votes)
return bytes
}
func (t *Topic) makeJson() []byte {
bytes, _ := json.Marshal(*t)
return bytes
}
func (t *Topic) vote(index uint64) {
t.Votes[index]++
}

var topics map[uint64]*Topic
var topicManager TopicManager

var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
Expand All @@ -58,12 +31,7 @@ var upgrader = websocket.Upgrader{

func GetTopicList(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
topicList := make([]Topic, 0)
for topicId := range topics {
if (!topics[topicId].Deleted) {
topicList = append(topicList, *topics[topicId])
}
}
topicList := topicManager. getTopicList()
bytes, _ := json.Marshal(topicList)
w.Write(bytes)
}
Expand All @@ -79,70 +47,61 @@ func PostTopic(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Topic Form is invalid", http.StatusBadRequest)
return
}
nextId := uint64(0)
for id := range topics {
if nextId <= id {
nextId = id + 1
}
}
topic := Topic {
nextId,
topicForm.Content,
topicForm.Choices,
make([]uint64, len(topicForm.Choices)),
false,
}
topics[topic.Id] = &topic
topic := topicManager.addTopic(&topicForm)
w.Header().Add("Content-Type", "application/json")
w.Write(topic.makeJson())
}

func GetTopic(w http.ResponseWriter, r *http.Request) {
_topic_id, err := strconv.Atoi(mux.Vars(r)["topic_id"])
topic_id := uint64(_topic_id)
topic, ok := topics[topic_id]
topic, ok := topicManager.getTopic(topic_id)
if err != nil || !ok {
http.Error(w, "Topic Id is invalid", http.StatusBadRequest)
return
}
}
w.Header().Add("Content-Type", "application/json")
w.Write(topic.makeJson())
}

func DeleteTopic(w http.ResponseWriter, r *http.Request) {
_topic_id, err := strconv.Atoi(mux.Vars(r)["topic_id"])
topic_id := uint64(_topic_id)
topic, ok := topics[topic_id]
_, ok := topicManager.getTopic(topic_id)
if err != nil || !ok {
http.Error(w, "Topic Id is invalid", http.StatusBadRequest)
return
}
topic.Deleted = true
}
ok = topicManager.deleteTopic(topic_id)
if !ok {
http.Error(w, "Delete Topic failed", http.StatusBadRequest)
return
}
}

func PostVote(w http.ResponseWriter, r *http.Request) {
_topic_id, err := strconv.Atoi(mux.Vars(r)["topic_id"])
topic_id := uint64(_topic_id)
topic, ok := topics[topic_id]
_, ok := topicManager.getTopic(topic_id)
if err != nil || !ok {
http.Error(w, "Topic Id is invalid", http.StatusBadRequest)
return
}
_vote_index, err := strconv.Atoi(mux.Vars(r)["vote_index"])
vote_index := uint64(_vote_index)
if err != nil || !topic.isValidVoteIndex(vote_index) {
http.Error(w, "Topic Id is invalid", http.StatusBadRequest)
vote_index, err := strconv.Atoi(mux.Vars(r)["vote_index"])
if err != nil {
http.Error(w, "Vote index is invalid", http.StatusBadRequest)
return
}
topic.vote(vote_index)
votes := topicManager.vote(topic_id, vote_index)
bytes, _ := json.Marshal(votes)
w.Header().Add("Content-Type", "application/json")
w.Write(topic.makeVotesJson())
w.Write(bytes)
}

func serveWs(w http.ResponseWriter, r *http.Request) {
_topic_id, err := strconv.Atoi(mux.Vars(r)["topic_id"])
topic_id := uint64(_topic_id)
topic, ok := topics[topic_id]
topic, ok := topicManager.getTopic(topic_id)
if err != nil || !ok {
http.Error(w, "Topic Id is invalid", http.StatusBadRequest)
return
Expand All @@ -161,20 +120,26 @@ func serveWs(w http.ResponseWriter, r *http.Request) {
defer tick.Stop()

conn.WriteJSON(topic.Votes)
for {
select {
case <- tick.C:
if topic.Deleted {
return
}
if err := conn.WriteJSON(topic.Votes); err != nil {
// log.Println("Websocket Connection Closed.")
return
}
for range tick.C {
if topic.Deleted {
return
}
if err := conn.WriteJSON(topic.Votes); err != nil {
// log.Println("Websocket Connection Closed.")
return
}
}
}

func flushDataPeriodically() {
tick := time.NewTicker(time.Second * 10)
defer tick.Stop()

for range tick.C {
saveTopics(topicManager.topics)
}
}

func router(router *mux.Router, port int) {
router.HandleFunc("/", serveHome)
router.HandleFunc("/ws/{topic_id}", func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -196,7 +161,7 @@ func router(router *mux.Router, port int) {
}).Handler(router)

addr := fmt.Sprintf("0.0.0.0:%d", port)
fmt.Printf("Server start. (%s)", addr)
log.Printf("Server start. (%s)", addr)
err := http.ListenAndServe(addr, handler)
if err != nil {
log.Fatalln("ListenAndServe: ", err)
Expand All @@ -207,21 +172,10 @@ func main() {
port := flag.Int("port", 8080, "Server port number")
flag.Parse()

topics = make(map[uint64]*Topic)
topics[1] = &Topic {
1,
"A vs B",
[]string{"A", "B"},
make([]uint64, 2),
false,
}
topics[2] = &Topic {
2,
"C vs D",
[]string{"C", "D"},
make([]uint64, 2),
false,
topicManager = TopicManager{
loadTopics(),
}
go flushDataPeriodically()

r := mux.NewRouter()
router(r, *port)
Expand Down
80 changes: 80 additions & 0 deletions LDLServer/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import "encoding/json"

type Topic struct {
Id uint64
Content string
Choices []string
Votes []uint64
Deleted bool
}

func (t Topic) isValidVoteIndex(index int) bool {
return index < len(t.Votes)
}
func (t *Topic) makeJson() []byte {
bytes, _ := json.Marshal(*t)
return bytes
}
func (t *Topic) vote(index int) {
t.Votes[index]++
}

type TopicForm struct {
Content string
Choices []string
}

type TopicManager struct {
topics map[uint64]*Topic
}
func (m *TopicManager) getTopicList() []Topic {
topicList := make([]Topic, 0)
for topicId := range m.topics {
if (!m.topics[topicId].Deleted) {
topicList = append(topicList, *m.topics[topicId])
}
}
return topicList
}
func (m *TopicManager) getTopic(topicId uint64) (*Topic, bool) {
topic, ok := m.topics[topicId]
return topic, ok
}
func (m *TopicManager) addTopic(topicForm *TopicForm) Topic {
nextId := uint64(0)
for id := range m.topics {
if nextId <= id {
nextId = id + 1
}
}
topic := Topic {
nextId,
topicForm.Content,
topicForm.Choices,
make([]uint64, len(topicForm.Choices)),
false,
}
m.topics[topic.Id] = &topic
return topic
}
func (m *TopicManager) deleteTopic(topicId uint64) bool {
topic, ok := m.topics[topicId]
if !ok {
return false
}
topic.Deleted = true
return true
}
func (m *TopicManager) vote(topicId uint64, voteIndex int) []uint64 {
topic, ok := m.topics[topicId]
if !ok {
return make([]uint64, 0)
}
if !topic.isValidVoteIndex(voteIndex) {
return make([]uint64, 0)
}
topic.vote(voteIndex)
return topic.Votes
}

0 comments on commit 9c8a830

Please sign in to comment.