Skip to content

Commit

Permalink
Add auto-sync on files changes
Browse files Browse the repository at this point in the history
  • Loading branch information
plutov committed Aug 30, 2024
1 parent b86e7e4 commit b82f63a
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 73 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ This approach offers a number of advantages, including:
- [x] Admin user authentication
- [x] Different database options: SQLite and Postgres
- [x] Continue where you left off
- [ ] Advanced validation rules
- [ ] Detect survey changes in real time
- [x] Advanced validation rules
- [x] Detect survey changes in real time
- [ ] Advanced question types
- [ ] Pipe answers into the following questions

## See it in Action!

Expand Down Expand Up @@ -153,6 +155,10 @@ Prompts users for a brief written answer.
```yaml
- type: short-text
label: What is the capital of Germany?
# set min/max characters
validation:
min: 10
max: 100
```

### Long Text
Expand All @@ -162,6 +168,10 @@ Prompts users for a detailed written answer.
```yaml
- type: long-text
label: What is the capital of Germany?
# set min/max characters
validation:
min: 10
max: 100
```

### Single Choice
Expand Down
3 changes: 2 additions & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module github.com/plutov/formulosity/api
go 1.22

require (
github.com/fsnotify/fsnotify v1.7.0
github.com/golang-migrate/migrate/v4 v4.17.1
github.com/google/uuid v1.6.0
github.com/labstack/echo/v4 v4.12.0
github.com/matoous/go-nanoid/v2 v2.0.0
github.com/mattn/go-sqlite3 v1.14.22
Expand All @@ -17,7 +19,6 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
Expand Down
14 changes: 12 additions & 2 deletions api/pkg/storage/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,22 @@ func (p *Sqlite) GetSurveySessionAnswers(sessionUUID string) ([]types.QuestionAn
answers := []types.QuestionAnswer{}
for rows.Next() {
answer := types.QuestionAnswer{}
var answerStr sql.NullString
err := rows.Scan(&answer.QuestionID, &answer.QuestionUUID, &answerStr)
var (
questionID sql.NullString
questionUUID sql.NullString
answerStr sql.NullString
)
err := rows.Scan(&questionID, &questionUUID, &answerStr)
if err != nil {
return nil, err
}

if !questionID.Valid || !questionUUID.Valid {
continue
}

answer.QuestionID = questionID.String
answer.QuestionUUID = questionUUID.String
answer.AnswerBytes = []byte(answerStr.String)
answers = append(answers, answer)
}
Expand Down
23 changes: 23 additions & 0 deletions api/pkg/surveys/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,34 @@ import (
"fmt"
"os"

"github.com/fsnotify/fsnotify"
"github.com/plutov/formulosity/api/pkg/log"
"github.com/plutov/formulosity/api/pkg/parser"
"github.com/plutov/formulosity/api/pkg/services"
)

func SyncSurveysOnChange(svc services.Services) {
watcher, _ := fsnotify.NewWatcher()
defer watcher.Close()

dir := os.Getenv("SURVEYS_DIR")

watcher.Add(dir)

done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
log.With("event", event).Info("file change event received")
SyncSurveys(svc)
}
}
}()

<-done
}

func SyncSurveys(svc services.Services) error {
logCtx := log.With("func", "SyncSurveys")
logCtx.Info("started surveys sync")
Expand Down
16 changes: 16 additions & 0 deletions api/pkg/types/answers.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ func (a TextAnswer) Value() (driver.Value, error) {
}

func (a *TextAnswer) Validate(q Question) error {
if q.Validation != nil && q.Validation.Min != nil && len(a.AnswerValue) < int(*q.Validation.Min) {
return fmt.Errorf("please write at least %d characters", *q.Validation.Min)
}
if q.Validation != nil && q.Validation.Max != nil && len(a.AnswerValue) > int(*q.Validation.Max) {
return fmt.Errorf("please write at most %d characters", *q.Validation.Max)
}

return nil
}

Expand Down Expand Up @@ -121,6 +128,15 @@ func (a NumberAnswer) Value() (driver.Value, error) {
}

func (a *NumberAnswer) Validate(q Question) error {
if q.Type == QuestionType_Rating {
if q.Min != nil && a.AnswerValue < int64(*q.Min) {
return fmt.Errorf("minimum: %d", *q.Min)
}
if q.Max != nil && a.AnswerValue > int64(*q.Max) {
return fmt.Errorf("maximum: %d", *q.Max)
}
}

return nil
}

Expand Down
68 changes: 34 additions & 34 deletions api/surveys/custom_theme/questions.yaml
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
questions:
- type: single-choice
id: question1 # optional ID, must be unique across all questions
label: What is the capital of Germany?
# defined in variables.yaml
optionsFromVariable: german-city-options
- type: multiple-choice
label: Which of the following are cities in Germany?
description: You can select multiple options
validation:
- type: single-choice
id: question1 # optional ID, must be unique across all questions
label: What is the capital of Germany?
# defined in variables.yaml
optionsFromVariable: german-city-options
- type: multiple-choice
label: Which of the following are cities in Germany?
description: You can select multiple options
validation:
min: 2
max: 4
options:
- Berlin
- Munich
- Paris
- London
- Hamburg
- Cologne
- Geneva
- Oslo
- type: short-text
label: What is the capital of Germany?
- type: long-text
label: What is the capital of Germany?
- type: date
label: When was the Berlin Wall built?
- type: rating
label: How much do you like Berlin?
min: 1
max: 3
options:
- Berlin
- Munich
- Paris
- London
- Hamburg
- Cologne
- Geneva
- Oslo
- type: short-text
label: What is the capital of Germany?
- type: long-text
label: What is the capital of Germany?
- type: date
label: When was the Berlin Wall built?
- type: rating
label: How much do you like Berlin?
min: 1
max: 5
- type: ranking
label: Rank the following cities by population
optionsFromVariable: german-city-options
- type: yes-no
label: Is Berlin the capital of Germany?
max: 5
- type: ranking
label: Rank the following cities by population
optionsFromVariable: german-city-options
- type: yes-no
label: Is Berlin the capital of Germany?
71 changes: 37 additions & 34 deletions api/surveys/simple/questions.yaml
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
questions:
- type: single-choice
id: question1 # optional ID, must be unique across all questions
label: What is the capital of Germany?
# defined in variables.yaml
optionsFromVariable: german-city-options
- type: multiple-choice
label: Which of the following are cities in Germany?
description: You can select multiple options
validation:
- type: single-choice
id: question1 # optional ID, must be unique across all questions
label: What is the capital of Germany?
# defined in variables.yaml
optionsFromVariable: german-city-options
- type: multiple-choice
label: Which of the following are cities in Germany?
description: You can select multiple options
validation:
min: 2
max: 4
options:
- Berlin
- Munich
- Paris
- London
- Hamburg
- Cologne
- Geneva
- Oslo
- type: short-text
label: What is the capital of Germany?
- type: long-text
label: What is the capital of Germany?
validation:
min: 10
max: 100
- type: date
label: When was the Berlin Wall built?
- type: rating
label: How much do you like Berlin?
min: 1
max: 3
options:
- Berlin
- Munich
- Paris
- London
- Hamburg
- Cologne
- Geneva
- Oslo
- type: short-text
label: What is the capital of Germany?
- type: long-text
label: What is the capital of Germany?
- type: date
label: When was the Berlin Wall built?
- type: rating
label: How much do you like Berlin?
min: 1
max: 5
- type: ranking
label: Rank the following cities by population
optionsFromVariable: german-city-options
- type: yes-no
label: Is Berlin the capital of Germany?
max: 5
- type: ranking
label: Rank the following cities by population
optionsFromVariable: german-city-options
- type: yes-no
label: Is Berlin the capital of Germany?
1 change: 1 addition & 0 deletions ui/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export async function call(path: string, init?: RequestInit, host?: string) {
data: data,
}
} catch (e) {
console.error('unable to call the api', e)
return {
status: 500,
error: 'unable to call the api',
Expand Down

0 comments on commit b82f63a

Please sign in to comment.