Skip to content

Commit b82f63a

Browse files
committed
Add auto-sync on files changes
1 parent b86e7e4 commit b82f63a

File tree

9 files changed

+139
-73
lines changed

9 files changed

+139
-73
lines changed

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ This approach offers a number of advantages, including:
3232
- [x] Admin user authentication
3333
- [x] Different database options: SQLite and Postgres
3434
- [x] Continue where you left off
35-
- [ ] Advanced validation rules
36-
- [ ] Detect survey changes in real time
35+
- [x] Advanced validation rules
36+
- [x] Detect survey changes in real time
37+
- [ ] Advanced question types
38+
- [ ] Pipe answers into the following questions
3739

3840
## See it in Action!
3941

@@ -153,6 +155,10 @@ Prompts users for a brief written answer.
153155
```yaml
154156
- type: short-text
155157
label: What is the capital of Germany?
158+
# set min/max characters
159+
validation:
160+
min: 10
161+
max: 100
156162
```
157163

158164
### Long Text
@@ -162,6 +168,10 @@ Prompts users for a detailed written answer.
162168
```yaml
163169
- type: long-text
164170
label: What is the capital of Germany?
171+
# set min/max characters
172+
validation:
173+
min: 10
174+
max: 100
165175
```
166176

167177
### Single Choice

api/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ module github.com/plutov/formulosity/api
33
go 1.22
44

55
require (
6+
github.com/fsnotify/fsnotify v1.7.0
67
github.com/golang-migrate/migrate/v4 v4.17.1
8+
github.com/google/uuid v1.6.0
79
github.com/labstack/echo/v4 v4.12.0
810
github.com/matoous/go-nanoid/v2 v2.0.0
911
github.com/mattn/go-sqlite3 v1.14.22
@@ -17,7 +19,6 @@ require (
1719
github.com/aymerick/douceur v0.2.0 // indirect
1820
github.com/davecgh/go-spew v1.1.1 // indirect
1921
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
20-
github.com/google/uuid v1.6.0 // indirect
2122
github.com/gorilla/css v1.0.1 // indirect
2223
github.com/hashicorp/errwrap v1.1.0 // indirect
2324
github.com/hashicorp/go-multierror v1.1.1 // indirect

api/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
1717
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
1818
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
1919
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
20+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
21+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
2022
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
2123
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
2224
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=

api/pkg/storage/sqlite.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,22 @@ func (p *Sqlite) GetSurveySessionAnswers(sessionUUID string) ([]types.QuestionAn
333333
answers := []types.QuestionAnswer{}
334334
for rows.Next() {
335335
answer := types.QuestionAnswer{}
336-
var answerStr sql.NullString
337-
err := rows.Scan(&answer.QuestionID, &answer.QuestionUUID, &answerStr)
336+
var (
337+
questionID sql.NullString
338+
questionUUID sql.NullString
339+
answerStr sql.NullString
340+
)
341+
err := rows.Scan(&questionID, &questionUUID, &answerStr)
338342
if err != nil {
339343
return nil, err
340344
}
341345

346+
if !questionID.Valid || !questionUUID.Valid {
347+
continue
348+
}
349+
350+
answer.QuestionID = questionID.String
351+
answer.QuestionUUID = questionUUID.String
342352
answer.AnswerBytes = []byte(answerStr.String)
343353
answers = append(answers, answer)
344354
}

api/pkg/surveys/sync.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,34 @@ import (
44
"fmt"
55
"os"
66

7+
"github.com/fsnotify/fsnotify"
78
"github.com/plutov/formulosity/api/pkg/log"
89
"github.com/plutov/formulosity/api/pkg/parser"
910
"github.com/plutov/formulosity/api/pkg/services"
1011
)
1112

13+
func SyncSurveysOnChange(svc services.Services) {
14+
watcher, _ := fsnotify.NewWatcher()
15+
defer watcher.Close()
16+
17+
dir := os.Getenv("SURVEYS_DIR")
18+
19+
watcher.Add(dir)
20+
21+
done := make(chan bool)
22+
go func() {
23+
for {
24+
select {
25+
case event := <-watcher.Events:
26+
log.With("event", event).Info("file change event received")
27+
SyncSurveys(svc)
28+
}
29+
}
30+
}()
31+
32+
<-done
33+
}
34+
1235
func SyncSurveys(svc services.Services) error {
1336
logCtx := log.With("func", "SyncSurveys")
1437
logCtx.Info("started surveys sync")

api/pkg/types/answers.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ func (a TextAnswer) Value() (driver.Value, error) {
9393
}
9494

9595
func (a *TextAnswer) Validate(q Question) error {
96+
if q.Validation != nil && q.Validation.Min != nil && len(a.AnswerValue) < int(*q.Validation.Min) {
97+
return fmt.Errorf("please write at least %d characters", *q.Validation.Min)
98+
}
99+
if q.Validation != nil && q.Validation.Max != nil && len(a.AnswerValue) > int(*q.Validation.Max) {
100+
return fmt.Errorf("please write at most %d characters", *q.Validation.Max)
101+
}
102+
96103
return nil
97104
}
98105

@@ -121,6 +128,15 @@ func (a NumberAnswer) Value() (driver.Value, error) {
121128
}
122129

123130
func (a *NumberAnswer) Validate(q Question) error {
131+
if q.Type == QuestionType_Rating {
132+
if q.Min != nil && a.AnswerValue < int64(*q.Min) {
133+
return fmt.Errorf("minimum: %d", *q.Min)
134+
}
135+
if q.Max != nil && a.AnswerValue > int64(*q.Max) {
136+
return fmt.Errorf("maximum: %d", *q.Max)
137+
}
138+
}
139+
124140
return nil
125141
}
126142

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
questions:
2-
- type: single-choice
3-
id: question1 # optional ID, must be unique across all questions
4-
label: What is the capital of Germany?
5-
# defined in variables.yaml
6-
optionsFromVariable: german-city-options
7-
- type: multiple-choice
8-
label: Which of the following are cities in Germany?
9-
description: You can select multiple options
10-
validation:
2+
- type: single-choice
3+
id: question1 # optional ID, must be unique across all questions
4+
label: What is the capital of Germany?
5+
# defined in variables.yaml
6+
optionsFromVariable: german-city-options
7+
- type: multiple-choice
8+
label: Which of the following are cities in Germany?
9+
description: You can select multiple options
10+
validation:
11+
min: 2
12+
max: 4
13+
options:
14+
- Berlin
15+
- Munich
16+
- Paris
17+
- London
18+
- Hamburg
19+
- Cologne
20+
- Geneva
21+
- Oslo
22+
- type: short-text
23+
label: What is the capital of Germany?
24+
- type: long-text
25+
label: What is the capital of Germany?
26+
- type: date
27+
label: When was the Berlin Wall built?
28+
- type: rating
29+
label: How much do you like Berlin?
1130
min: 1
12-
max: 3
13-
options:
14-
- Berlin
15-
- Munich
16-
- Paris
17-
- London
18-
- Hamburg
19-
- Cologne
20-
- Geneva
21-
- Oslo
22-
- type: short-text
23-
label: What is the capital of Germany?
24-
- type: long-text
25-
label: What is the capital of Germany?
26-
- type: date
27-
label: When was the Berlin Wall built?
28-
- type: rating
29-
label: How much do you like Berlin?
30-
min: 1
31-
max: 5
32-
- type: ranking
33-
label: Rank the following cities by population
34-
optionsFromVariable: german-city-options
35-
- type: yes-no
36-
label: Is Berlin the capital of Germany?
31+
max: 5
32+
- type: ranking
33+
label: Rank the following cities by population
34+
optionsFromVariable: german-city-options
35+
- type: yes-no
36+
label: Is Berlin the capital of Germany?

api/surveys/simple/questions.yaml

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,39 @@
11
questions:
2-
- type: single-choice
3-
id: question1 # optional ID, must be unique across all questions
4-
label: What is the capital of Germany?
5-
# defined in variables.yaml
6-
optionsFromVariable: german-city-options
7-
- type: multiple-choice
8-
label: Which of the following are cities in Germany?
9-
description: You can select multiple options
10-
validation:
2+
- type: single-choice
3+
id: question1 # optional ID, must be unique across all questions
4+
label: What is the capital of Germany?
5+
# defined in variables.yaml
6+
optionsFromVariable: german-city-options
7+
- type: multiple-choice
8+
label: Which of the following are cities in Germany?
9+
description: You can select multiple options
10+
validation:
11+
min: 2
12+
max: 4
13+
options:
14+
- Berlin
15+
- Munich
16+
- Paris
17+
- London
18+
- Hamburg
19+
- Cologne
20+
- Geneva
21+
- Oslo
22+
- type: short-text
23+
label: What is the capital of Germany?
24+
- type: long-text
25+
label: What is the capital of Germany?
26+
validation:
27+
min: 10
28+
max: 100
29+
- type: date
30+
label: When was the Berlin Wall built?
31+
- type: rating
32+
label: How much do you like Berlin?
1133
min: 1
12-
max: 3
13-
options:
14-
- Berlin
15-
- Munich
16-
- Paris
17-
- London
18-
- Hamburg
19-
- Cologne
20-
- Geneva
21-
- Oslo
22-
- type: short-text
23-
label: What is the capital of Germany?
24-
- type: long-text
25-
label: What is the capital of Germany?
26-
- type: date
27-
label: When was the Berlin Wall built?
28-
- type: rating
29-
label: How much do you like Berlin?
30-
min: 1
31-
max: 5
32-
- type: ranking
33-
label: Rank the following cities by population
34-
optionsFromVariable: german-city-options
35-
- type: yes-no
36-
label: Is Berlin the capital of Germany?
34+
max: 5
35+
- type: ranking
36+
label: Rank the following cities by population
37+
optionsFromVariable: german-city-options
38+
- type: yes-no
39+
label: Is Berlin the capital of Germany?

ui/src/lib/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export async function call(path: string, init?: RequestInit, host?: string) {
3434
data: data,
3535
}
3636
} catch (e) {
37+
console.error('unable to call the api', e)
3738
return {
3839
status: 500,
3940
error: 'unable to call the api',

0 commit comments

Comments
 (0)