First commit.

This commit is contained in:
Andrea Fazzi 2022-06-23 11:25:35 +02:00
commit cd7e27504a
24 changed files with 854 additions and 0 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Probo Collector
A backend to collect quizzes in a RESTful way.

27
client/client.go Normal file
View file

@ -0,0 +1,27 @@
package client
import "git.andreafazzi.eu/andrea/testhub/models"
type Response struct {
Status string `json:"status"`
Content interface{} `json:"content"`
}
type QuizReadAllResponse struct {
Status string `json:"status"`
Content []*models.Quiz `json:"content"`
}
type CreateQuestionRequest struct {
Text string `json:"text"`
}
type CreateAnswerRequest struct {
Text string
Correct bool
}
type CreateQuizRequest struct {
Question *CreateQuestionRequest `json:"question"`
Answers []*CreateAnswerRequest `json:"answers"`
}

13
go.mod Normal file
View file

@ -0,0 +1,13 @@
module git.andreafazzi.eu/andrea/testhub
go 1.17
require (
github.com/google/uuid v1.3.0 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

18
go.sum Normal file
View file

@ -0,0 +1,18 @@
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 h1:nRDwTcxV9B3elxMt+1xINX0bwaPdpouqp5fbynexY8U=
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

112
logger/logger.go Normal file
View file

@ -0,0 +1,112 @@
package logger
import (
"log"
"net/http"
"time"
"github.com/sirupsen/logrus"
)
const (
Disabled = iota
InfoLevel
WarningLevel
DebugLevel
)
type (
// struct for holding response details
responseData struct {
status int
size int
}
// our http.ResponseWriter implementation
loggingResponseWriter struct {
http.ResponseWriter // compose original http.ResponseWriter
responseData *responseData
}
)
var loggerLevel int = InfoLevel
func (r *loggingResponseWriter) Write(b []byte) (int, error) {
size, err := r.ResponseWriter.Write(b) // write response using original http.ResponseWriter
r.responseData.size += size // capture size
return size, err
}
func (r *loggingResponseWriter) WriteHeader(statusCode int) {
r.ResponseWriter.WriteHeader(statusCode) // write status code using original http.ResponseWriter
r.responseData.status = statusCode // capture status code
}
func WithLogging(h http.Handler) http.Handler {
loggingFn := func(rw http.ResponseWriter, req *http.Request) {
start := time.Now()
responseData := &responseData{
status: 0,
size: 0,
}
lrw := loggingResponseWriter{
ResponseWriter: rw, // compose original http.ResponseWriter
responseData: responseData,
}
h.ServeHTTP(&lrw, req) // inject our implementation of http.ResponseWriter
duration := time.Since(start)
if loggerLevel >= InfoLevel {
logrus.WithFields(logrus.Fields{
"URI": req.RequestURI,
"Method": req.Method,
"Status": responseData.status,
"Duration": duration,
"Size": responseData.size,
}).Info("Completed.")
}
}
return http.HandlerFunc(loggingFn)
}
func SetLevel(level int) {
loggerLevel = level
}
func Info(v ...interface{}) {
if loggerLevel >= InfoLevel {
log.Println(v...)
}
}
func Infof(format string, v ...interface{}) {
if loggerLevel >= InfoLevel {
log.Printf(format, v...)
}
}
func Warning(v ...interface{}) {
if loggerLevel >= WarningLevel {
log.Println(v...)
}
}
func Warningf(format string, v ...interface{}) {
if loggerLevel >= WarningLevel {
log.Printf(format, v...)
}
}
func Debug(v ...interface{}) {
if loggerLevel >= DebugLevel {
log.Println(v...)
}
}
func Debugf(format string, v ...interface{}) {
if loggerLevel >= DebugLevel {
log.Printf(format, v...)
}
}

23
main.go Normal file
View file

@ -0,0 +1,23 @@
package main
import (
"log"
"net/http"
"git.andreafazzi.eu/andrea/testhub/logger"
"git.andreafazzi.eu/andrea/testhub/store"
"github.com/sirupsen/logrus"
)
const port = "3000"
func main() {
logger.SetLevel(logger.DebugLevel)
server := NewQuizHubCollectorServer(store.NewMemoryQuizHubCollectorStore())
addr := "localhost:" + port
logrus.WithField("addr", addr).Info("TestHub Collector server is listening.")
log.Fatal(http.ListenAndServe(":"+port, server))
}

121
misc/logseq/.gitignore vendored Normal file
View file

@ -0,0 +1,121 @@
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
*~

21
misc/logseq/LICENSE.md Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2022 Andrea Fazzi
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
misc/logseq/README.md Normal file
View file

@ -0,0 +1,4 @@
# What's that?
A very basic boilerplate useful to start devoloping a
[Logseq](https://logseq.com/) plugin.

10
misc/logseq/icon.svg Normal file
View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hierarchy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="gray" fill="gray" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="5" r="2" />
<circle cx="5" cy="19" r="2" />
<circle cx="19" cy="19" r="2" />
<path d="M6.5 17.5l5.5 -4.5l5.5 4.5" />
<line x1="12" y1="7" x2="12" y2="13" />
</svg>

After

Width:  |  Height:  |  Size: 471 B

29
misc/logseq/package.json Normal file
View file

@ -0,0 +1,29 @@
{
"logseq": {
"id": "logseq-helloworld-plugin",
"title": "logseq-helloworld-plugin",
"icon": "./icon.svg"
},
"name": "logseq-helloworld-plugin",
"version": "1.2.0",
"description": "",
"main": "dist/index.html",
"targets": {
"main": false
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "parcel build --no-source-maps src/index.html --public-url ./"
},
"keywords": [],
"author": "Andrea Fazzi",
"license": "MIT",
"dependencies": {
"@logseq/libs": "^0.0.1-alpha.35",
"js-base64": "^3.7.2"
},
"devDependencies": {
"buffer": "^6.0.3",
"parcel": "^2.2.0"
}
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="index.ts" type="module"></script>
</body>
</html>

101
misc/logseq/src/index.ts Normal file
View file

@ -0,0 +1,101 @@
import "@logseq/libs";
import { BlockEntity } from "@logseq/libs/dist/LSPlugin.user";
const endpoint = 'http://localhost:3000/quizzes';
const uniqueIdentifier = () =>
Math.random()
.toString(36)
.replace(/[^a-z]+/g, "");
async function fetchQuizzes() {
const { status: status, content: quizzes } = await fetch(endpoint).then(res => res.json())
const ret = quizzes || []
return ret.map((quiz, i) => {
return `${i + 1}. ${quiz.Question.Text}`
})
}
const main = () => {
console.log("logseq-quizhub-plugin LOADED!");
logseq.Editor.registerSlashCommand("Get All Quizzes", async () => {
const currBlock = await logseq.Editor.getCurrentBlock();
let blocks = await fetchQuizzes()
blocks = blocks.map((it: BlockEntity) => ({ content: it }))
await logseq.Editor.insertBatchBlock(currBlock.uuid, blocks, {
sibling: false
})
});
logseq.Editor.registerSlashCommand("Insert a batch of quizzes", async () => {
await logseq.Editor.insertAtEditingCursor(
`{{renderer :quizhub_${uniqueIdentifier()}}}`
);
const currBlock = await logseq.Editor.getCurrentBlock();
await logseq.Editor.insertBlock(currBlock.uuid, 'Text of the question here...',
{
sibling: false,
before: false,
}
);
await logseq.Editor.exitEditingMode();
});
logseq.App.onMacroRendererSlotted(async ({ slot, payload }) => {
const [type] = payload.arguments;
const id = type.split("_")[1]?.trim();
const quizhubId = `quizhub_${id}`;
logseq.provideModel({
async postQuiz() {
const parentBlock = await logseq.Editor.getBlock(payload.uuid, { includeChildren: true });
const quizzes = parentBlock.children.map((child: BlockEntity) => {
const question = { text: child.content }
const answers = child.children.map((answer: BlockEntity, i: number) => {
return { text: answer.content, correct: (i == 0) ? true : false }
})
return { question: question, answers: answers }
});
quizzes.forEach(async quiz => {
const res = await fetch(endpoint, { method: 'POST', body: JSON.stringify(quiz) })
const data = await res.json();
console.log(data)
})
}
});
logseq.provideStyle(`
.renderBtn {
border: 1px solid black;
border-radius: 8px;
padding: 3px;
font-size: 80%;
background-color: white;
color: black;
}
.renderBtn:hover {
background-color: black;
color: white;
}
`);
logseq.provideUI({
key: `${quizhubId}`,
slot,
reset: true,
template: `<button data-on-click="postQuiz" class="renderBtn">Save</button>`,
});
});
};
logseq.ready(main).catch(console.error);

6
models/answer.go Normal file
View file

@ -0,0 +1,6 @@
package models
type Answer struct {
ID string
Text string
}

6
models/player.go Normal file
View file

@ -0,0 +1,6 @@
package models
type Player struct {
Name string
Wins int
}

7
models/question.go Normal file
View file

@ -0,0 +1,7 @@
package models
type Question struct {
ID string
Text string
AnswerIDs []string
}

10
models/test.go Normal file
View file

@ -0,0 +1,10 @@
package models
type Quiz struct {
ID uint
Question *Question
Answers []*Answer
Correct *Answer
Type int
}

68
server.go Normal file
View file

@ -0,0 +1,68 @@
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"git.andreafazzi.eu/andrea/testhub/client"
"git.andreafazzi.eu/andrea/testhub/logger"
"git.andreafazzi.eu/andrea/testhub/models"
"git.andreafazzi.eu/andrea/testhub/store"
)
const jsonContentType = "application/json"
type QuizHubCollectorServer struct {
store store.QuizHubCollectorStore
http.Handler
}
func NewQuizHubCollectorServer(store store.QuizHubCollectorStore) *QuizHubCollectorServer {
ps := new(QuizHubCollectorServer)
ps.store = store
router := http.NewServeMux()
router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
ps.Handler = router
return ps
}
func (ps *QuizHubCollectorServer) testHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.Header().Set("content-type", jsonContentType)
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
case http.MethodPost:
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(ps.createQuiz(w, r))
}
}
func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.Request) *client.Response {
tests, err := ps.store.ReadAllQuizzes()
if err != nil {
return &client.Response{Status: "error", Content: err.Error()}
}
return &client.Response{Status: "success", Content: tests}
}
func (ps *QuizHubCollectorServer) createQuiz(w http.ResponseWriter, r *http.Request) *models.Quiz {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
createQuizReq := new(client.CreateQuizRequest)
err = json.Unmarshal(body, &createQuizReq)
if err != nil {
panic(err)
}
createdQuiz := ps.store.CreateQuiz(createQuizReq)
return createdQuiz
}

View file

@ -0,0 +1,69 @@
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"git.andreafazzi.eu/andrea/testhub/client"
"git.andreafazzi.eu/andrea/testhub/models"
"git.andreafazzi.eu/andrea/testhub/store"
"github.com/remogatto/prettytest"
)
type integrationTestSuite struct {
prettytest.Suite
}
func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
server := NewQuizHubCollectorServer(store.NewMemoryQuizHubCollectorStore())
// POST a new question using a JSON payload
payload := `
{
"question": {"text": "Question 1"},
"answers": [
{"text": "Text of the answer 1", "correct": true},
{"text": "Text of the answer 2"},
{"text": "Text of the answer 3"},
{"text": "Text of the answer 4"}
]
}
`
request, _ := http.NewRequest(http.MethodPost, "/quizzes", strings.NewReader(payload))
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
returnedTest := new(models.Quiz)
err := json.Unmarshal(response.Body.Bytes(), returnedTest)
if err != nil {
t.True(err == nil, err.Error())
}
t.Equal("Question 1", returnedTest.Question.Text)
t.Equal("Text of the answer 1", returnedTest.Answers[0].Text)
t.Equal("Text of the answer 1", returnedTest.Correct.Text)
t.True(returnedTest.ID != 0, "Test ID should not be 0")
t.True(returnedTest.Question.ID != "", "Question ID should not be empty")
t.True(returnedTest.Answers[0].ID != "", "Answer ID should not be empty")
request, _ = http.NewRequest(http.MethodGet, "/quizzes", nil)
response = httptest.NewRecorder()
server.ServeHTTP(response, request)
decodedResponse := new(client.QuizReadAllResponse)
err = json.Unmarshal(response.Body.Bytes(), &decodedResponse)
if err != nil {
t.True(err == nil, err.Error())
}
t.True(len(decodedResponse.Content) == 1, "Length of returned tests should be 1")
t.Equal("Question 1", decodedResponse.Content[0].Question.Text)
}

90
server_test.go Normal file
View file

@ -0,0 +1,90 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"git.andreafazzi.eu/andrea/testhub/client"
"git.andreafazzi.eu/andrea/testhub/logger"
"git.andreafazzi.eu/andrea/testhub/models"
"github.com/remogatto/prettytest"
)
type testSuite struct {
prettytest.Suite
}
type StubTestHubCollectorStore struct {
tests []*models.Quiz
}
func (store *StubTestHubCollectorStore) CreateQuiz(test *client.CreateQuizRequest) *models.Quiz {
return nil
}
func (store *StubTestHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
return store.tests, nil
}
func TestRunner(t *testing.T) {
prettytest.Run(
t,
new(testSuite),
new(integrationTestSuite),
)
}
func (t *testSuite) BeforeAll() {
logger.SetLevel(logger.Disabled)
}
func (t *testSuite) TestGETQuestions() {
expectedResult := &client.QuizReadAllResponse{
Status: "success",
Content: []*models.Quiz{
{
Question: &models.Question{ID: "1", Text: "Question 1"},
Answers: []*models.Answer{{}, {}, {}},
},
},
}
store := &StubTestHubCollectorStore{[]*models.Quiz{
{
Question: &models.Question{ID: "1", Text: "Question 1"},
Answers: []*models.Answer{{}, {}, {}},
},
}}
server := NewQuizHubCollectorServer(store)
request, _ := http.NewRequest(http.MethodGet, "/quizzes", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
result := getResponse(response.Body)
t.True(result.Status == expectedResult.Status)
t.True(testsAreEqual(result, expectedResult))
t.Equal(http.StatusOK, response.Code)
}
func getResponse(body io.Reader) (response *client.QuizReadAllResponse) {
err := json.NewDecoder(body).Decode(&response)
if err != nil {
panic(fmt.Errorf("Unable to parse response from server %q into slice of Test, '%v'", body, err))
}
return
}
func testsAreEqual(got, want *client.QuizReadAllResponse) bool {
return reflect.DeepEqual(got, want)
}

81
store/memory.go Normal file
View file

@ -0,0 +1,81 @@
package store
import (
"crypto/sha256"
"fmt"
"sync"
"git.andreafazzi.eu/andrea/testhub/client"
"git.andreafazzi.eu/andrea/testhub/models"
)
type MemoryQuizHubCollectorStore struct {
questions map[string]*models.Question
answers map[string]*models.Answer
tests map[uint]*models.Quiz
lastQuizID uint
questionAnswer map[string][]string
testQuestion map[string]uint
// A mutex is used to synchronize read/write access to the map
lock sync.RWMutex
}
func NewMemoryQuizHubCollectorStore() *MemoryQuizHubCollectorStore {
s := new(MemoryQuizHubCollectorStore)
s.questions = make(map[string]*models.Question)
s.answers = make(map[string]*models.Answer)
s.tests = make(map[uint]*models.Quiz)
return s
}
func (s *MemoryQuizHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
result := make([]*models.Quiz, 0)
for _, t := range s.tests {
result = append(result, t)
}
return result, nil
}
func (s *MemoryQuizHubCollectorStore) CreateQuiz(r *client.CreateQuizRequest) *models.Quiz {
questionID := hash(r.Question.Text)
test := new(models.Quiz)
q, ok := s.questions[questionID]
if !ok { // if the question is not in the store add it
s.questions[questionID] = &models.Question{
ID: questionID,
Text: r.Question.Text,
}
q = s.questions[questionID]
}
// Populate Question field
test.Question = q
for _, answer := range r.Answers {
// Calculate the hash from text
answerID := hash(answer.Text)
_, ok := s.answers[answerID]
if !ok { // if the answer is not in the store add it
s.answers[answerID] = &models.Answer{
ID: answerID,
Text: answer.Text,
}
}
if answer.Correct {
test.Correct = s.answers[answerID]
}
test.Answers = append(test.Answers, s.answers[answerID])
}
s.lastQuizID++
test.ID = s.lastQuizID
s.tests[s.lastQuizID] = test
return test
}
func hash(text string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(text)))
}

11
store/store.go Normal file
View file

@ -0,0 +1,11 @@
package store
import (
"git.andreafazzi.eu/andrea/testhub/client"
"git.andreafazzi.eu/andrea/testhub/models"
)
type QuizHubCollectorStore interface {
ReadAllQuizzes() ([]*models.Quiz, error)
CreateQuiz(r *client.CreateQuizRequest) *models.Quiz
}

View file

@ -0,0 +1,10 @@
POST http://localhost:3000/quizzes
{
"question": {"text": "Text of Question 1"},
"answers": [
{"text": "Text of the answer 1", "correct": true},
{"text": "Text of the answer 2"},
{"text": "Text of the answer 3"},
{"text": "Text of the answer 4"}
]
}

View file

@ -0,0 +1 @@
GET http://localhost:3000/quizzes