Merge branch 'master' into production

This commit is contained in:
Andrea Fazzi 2020-12-24 14:07:55 +01:00
commit 7df7386a97
16 changed files with 280 additions and 15 deletions

View file

@ -1,5 +1,14 @@
FROM golang:latest ############################
# STEP 1 build executable binary
############################
FROM golang:alpine AS builder
# Install git.
# Git is required for fetching the dependencies.
RUN apk update && apk add --no-cache git
ENV PATH=$PATH:/src/oef/ ENV PATH=$PATH:/src/oef/
RUN mkdir -p /src/oef RUN mkdir -p /src/oef
ADD . /src/oef ADD . /src/oef
@ -8,7 +17,48 @@ COPY config/config.aws.yaml /src/oef/config/config.yaml
WORKDIR /src/oef/ WORKDIR /src/oef/
RUN go build -o oef *.go RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o oef *.go
# RUN go build -o oef *.go
############################
# STEP 2 build a small image
############################
FROM scratch
# Copy our static executable.
COPY --from=builder /src/oef/oef /src/oef/oef
# Copy config file
COPY --from=builder /src/oef/config/config.yaml /src/oef/config/config.yaml
# Copy templates
COPY --from=builder /src/oef/templates /src/oef/templates
# Copy dist
COPY --from=builder /src/oef/dist /src/oef/dist
# Copy VERSION file
COPY --from=builder /src/oef/VERSION /src/oef/VERSION
WORKDIR /src/oef/
# Run the oef binary.
ENTRYPOINT ["/src/oef/oef"] ENTRYPOINT ["/src/oef/oef"]
EXPOSE 80 EXPOSE 80
# FROM golang:latest
# ENV PATH=$PATH:/src/oef/
# RUN mkdir -p /src/oef
# ADD . /src/oef
# COPY config/config.aws.yaml /src/oef/config/config.yaml
# WORKDIR /src/oef/
# RUN go build -o oef *.go
# ENTRYPOINT ["/src/oef/oef"]
# EXPOSE 80

23
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

10
docker/oef_aws/db.env Normal file
View file

@ -0,0 +1,10 @@
MYSQL_ROOT_PASSWORD=oef
MYSQL_PASSWORD=oef
MYSQL_DATABASE=oef_prod
MYSQL_USER=oef

View file

@ -0,0 +1,14 @@
version: "3.3"
services:
app:
image: andrea/oef:latest
ports:
- 80:80
environment:
- DB_HOST=oef-micro.cjsemjfwbreb.eu-central-1.rds.amazonaws.com
- DB_PORT=3306

View file

@ -42,6 +42,7 @@ services:
- "4444:4444" - "4444:4444"
- "5900:5900" - "5900:5900"
volumes: volumes:
- /etc/localtime:/etc/localtime:ro
- /dev/shm:/dev/shm - /dev/shm:/dev/shm
volumes: volumes:

View file

@ -240,14 +240,25 @@ func (model *Response) Update(db *Database, args map[string]string, w http.Respo
} }
// Check if the contest is still active // Check if the contest is still active
if !response.(*Response).IsActive() { if isParticipant(r) && !response.(*Response).IsActive() {
return nil, errors.OutOfTime return nil, errors.OutOfTime
} }
response.(*Response).SingleResponses = make([]*SingleResponse, 0)
err = renderer.Decode(response, r) err = renderer.Decode(response, r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If there aren't single responses then participant
// response Starttime is resetted.
if isAdministrator(r) {
if len(response.(*Response).SingleResponses) == 0 {
response.(*Response).StartTime = time.Time{}
}
}
WriteUpdater(r, response.(*Response)) WriteUpdater(r, response.(*Response))
_, err = SaveResponse(db, response) _, err = SaveResponse(db, response)

View file

@ -231,6 +231,106 @@ func (t *testSuite) TestSchoolSubscription() {
} }
func (t *testSuite) TestParticipantResponse() {
login("WINBOXGUF", "GUf3wIP0DF7")
err := findElement("a.btn-primary").Click()
if err != nil {
panic(err)
}
err = findElement("#update-response").Click()
if err != nil {
panic(err)
}
correct := []string{
"97", "129", "105", "113", "93", "89", "109", "141", "157",
"85", "133", "153", "121", "117", "101", "149", "81", "145",
"125", "137",
}
for _, answerId := range correct {
err = findElement("#answer_" + answerId).Click()
if err != nil {
panic(err)
}
}
err = findElement("button.btn-primary").Click()
if err != nil {
panic(err)
}
logout()
login("admin", "admin")
err = findElement("#responses_navbar_link").Click()
if err != nil {
panic(err)
}
err = findElement("#myInput").SendKeys("BOXILL")
if err != nil {
panic(err)
}
err = findElement("a.oef-selected-by-search").Click()
if err != nil {
panic(err)
}
scoreEl := findElement("#score")
score, err := scoreEl.Text()
t.Nil(err)
t.Equal("20", score)
err = findElement("a.btn-primary").Click()
if err != nil {
panic(err)
}
err = findElement("#reset_responses").Click()
if err != nil {
panic(err)
}
err = findElement("button.btn-primary").Click()
if err != nil {
panic(err)
}
logout()
}
func (t *testSuite) TestContestActiveWindow() {
login("admin", "admin")
err := searchAndClick("junior")
if err != nil {
panic(err)
}
err = findElement("a.btn-primary").Click()
if err != nil {
panic(err)
}
err = findElement("#contest_description").SendKeys("Gara per la categoria JUNIOR")
err = findElement("#contest_date").SendKeys("2020-03-04")
err = findElement("#contest_start_time").SendKeys("14:00")
err = findElement("#contest_end_time").SendKeys("15:00")
err = findElement("#contest_duration").SendKeys("45")
err = findElement(".btn-primary").Click()
t.True(true)
logout()
}
func findElement(selector string) selenium.WebElement { func findElement(selector string) selenium.WebElement {
element, err := wd.FindElement(selenium.ByCSSSelector, selector) element, err := wd.FindElement(selenium.ByCSSSelector, selector)
if err != nil { if err != nil {
@ -436,3 +536,16 @@ func clickPrimaryButton() {
panic(err) panic(err)
} }
} }
func searchAndClick(text string) error {
err := findElement("#myInput").SendKeys(text)
if err != nil {
return err
}
err = findElement("a.oef-selected-by-search").Click()
if err != nil {
return err
}
return nil
}

1
scripts/export_schools/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.csv

Binary file not shown.

View file

@ -0,0 +1,36 @@
package main
import (
"flag"
"os"
"git.andreafazzi.eu/andrea/oef/client"
"git.andreafazzi.eu/andrea/oef/orm"
"github.com/gocarina/gocsv"
)
func main() {
username := flag.String("username", "admin", "Username")
password := flag.String("password", "admin", "Password")
output := flag.String("output", "schools.csv", "Output filename")
flag.Parse()
client, err := client.Dial(flag.Arg(0), *username, *password)
if err != nil {
panic(err)
}
schools := make([]*orm.School, 0)
err = client.ReadAll(&schools)
if err != nil {
panic(err)
}
f, err := os.Create(*output)
if err != nil {
panic(err)
}
defer f.Close()
gocsv.MarshalFile(schools, f)
}

Binary file not shown.

View file

@ -2,7 +2,7 @@ import './style.css'
$(function () { $(function () {
setInterval(function() { setInterval(function() {
var timeleft = parseInt($("#timeleft").html()); var timeleft = parseInt($("#timeleft").html());
if (timeleft > 0) { if (timeleft > 0) {
@ -10,7 +10,13 @@ $(function () {
} else { } else {
timeleft = 0; timeleft = 0;
} }
$("#timeleft").html(timeleft)
$("#timeleft").html(timeleft);
var minutes = Math.floor(timeleft/60);
var seconds = timeleft % 60;
$("#timeleft_view").html(minutes+":"+seconds.toString().padStart(2, "0"));
}, 1000); }, 1000);
$("#reloadCaptcha").on("click",function(eventObject) { $("#reloadCaptcha").on("click",function(eventObject) {
@ -66,4 +72,8 @@ $(function () {
$(".karmen-ajax-delete").on("click", deleteHandler); $(".karmen-ajax-delete").on("click", deleteHandler);
$("#reset_responses").on("click", function(eventObject) {
$('.form-check-input').removeAttr('checked');
});
}); });

View file

@ -45,7 +45,7 @@
<a class="nav-item nav-link {{.Options|active "Answer"}}" href="{{all "Answer"}}">Risposte</a> <a class="nav-item nav-link {{.Options|active "Answer"}}" href="{{all "Answer"}}">Risposte</a>
<a id="schools_navbar_link" class="nav-item nav-link {{.Options|active "School"}}" href="{{all "School"}}">Scuole</a> <a id="schools_navbar_link" class="nav-item nav-link {{.Options|active "School"}}" href="{{all "School"}}">Scuole</a>
<a id="participants_navbar_link" class="nav-item nav-link {{.Options|active "Participant"}}" href="{{all "Participant"}}">Partecipanti</a> <a id="participants_navbar_link" class="nav-item nav-link {{.Options|active "Participant"}}" href="{{all "Participant"}}">Partecipanti</a>
<a class="nav-item nav-link {{.Options|active "Response"}}" href="{{all "Response"}}">Prove</a> <a id="responses_navbar_link" class="nav-item nav-link {{.Options|active "Response"}}" href="{{all "Response"}}">Prove</a>
{{- end -}} {{- end -}}
{{- if $isSchool -}} {{- if $isSchool -}}
<a id="school_navbar_link" class="nav-item nav-link {{.Options|active "School"}}" href="{{.Claims|modelId|show "School"}}">Scuola</a> <a id="school_navbar_link" class="nav-item nav-link {{.Options|active "School"}}" href="{{.Claims|modelId|show "School"}}">Scuola</a>

View file

@ -37,7 +37,7 @@
<footer class="footer text-center"> <footer class="footer text-center">
<div class="container"> <div class="container">
<span class="text-center text-muted">© 2020 <a href="https://www.olimpiadi-economiaefinanza.it">Olimpiadi di Economia e Finanza 2020</a> ({{version}}) <span class="text-center text-muted">© 2020 <a href="https://www.olimpiadi-economiaefinanza.it">Olimpiadi di Economia e Finanza</a> ({{version}})
</span> </span>
</div> </div>
</footer> </footer>

View file

@ -46,7 +46,7 @@
{{if isResponseIn $answer.ID $.Data.AnswersIDs}} {{if isResponseIn $answer.ID $.Data.AnswersIDs}}
{{$checked = true}} {{$checked = true}}
{{end}} {{end}}
<input class="form-check-input" type="radio" name="SingleResponses.{{$id}}" id="answer_{{$answer.ID}}" value="{{$answer.ID}}" required {{if $checked}}checked{{end}}> <input class="form-check-input" type="radio" name="SingleResponses.{{$id}}" id="answer_{{$answer.ID}}" value="{{$answer.ID}}" {{if $isParticipant}}required{{end}} {{if $checked}}checked{{end}}>
<label class="form-check-label {{if and $isAdmin $answer.Correct}}text-success{{end}}" for="answer_{{$answer.ID}}"> <label class="form-check-label {{if and $isAdmin $answer.Correct}}text-success{{end}}" for="answer_{{$answer.ID}}">
{{$answer}} {{$answer}}
</label> </label>
@ -71,10 +71,12 @@
<dt>Gara</dt><dd>{{.Data.Contest}}</dd> <dt>Gara</dt><dd>{{.Data.Contest}}</dd>
<dt>Partecipante</dt><dd>{{.Data.Participant}}</dd> <dt>Partecipante</dt><dd>{{.Data.Participant}}</dd>
{{- if $isParticipant -}} {{- if $isParticipant -}}
<dt>Tempo rimanente</dt>{{if .Data.TimeLeft}}<dd><span id="timeleft">{{.Data.TimeLeft.Seconds|toInt}}</span> secondi rimanenti</dd>{{else}}<dd>La gara è sempre attiva</dd>{{end}} <span id="timeleft" style="display: none">{{.Data.TimeLeft.Seconds|toInt}}</span>
<dt>Tempo rimanente</dt>{{if .Data.TimeLeft}}<dd><span id="timeleft_view"></span> minuti rimanenti</dd>{{else}}<dd>La gara è sempre attiva</dd>{{end}}
{{- end -}} {{- end -}}
</dl> </dl>
<button type="submit" class="btn btn-primary" form="{{$form}}">Salva</button> <button type="submit" class="btn btn-primary" form="{{$form}}">Salva</button>
{{if $isAdmin}}<button id="reset_responses" class="btn btn-outline-secondary">Cancella risposte</button>{{end}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -48,7 +48,7 @@
<dd class="col-sm-9">nessuna risposta fornita</dd> <dd class="col-sm-9">nessuna risposta fornita</dd>
{{end}} {{end}}
<dt class="col-sm-3">Punteggio</dt> <dt class="col-sm-3">Punteggio</dt>
<dd class="col-sm-9">{{.Data.Score}}</dd> <dd id="score" class="col-sm-9">{{.Data.Score}}</dd>
{{if $creatorUser:=.Data.CreatedBy}} {{if $creatorUser:=.Data.CreatedBy}}
<dt class="col-sm-3">Creato da</dt> <dt class="col-sm-3">Creato da</dt>
<dd class="col-sm-9">{{$creatorUser.Username}}[{{$creatorUser.Role}}] {{$.Data.CreatedAt|prettyDateTime}} da {{.Data.CreatorIP}}</dd> <dd class="col-sm-9">{{$creatorUser.Username}}[{{$creatorUser.Role}}] {{$.Data.CreatedAt|prettyDateTime}} da {{.Data.CreatorIP}}</dd>
@ -76,7 +76,7 @@
{{if .Data.IsActive}} {{if .Data.IsActive}}
<p> <p>
Per iniziare o riprendere la gara clicca sul pulsante a destra. Per iniziare o riprendere la gara clicca sul pulsante a destra.
<a id="new_participant_subscribe" href="{{.Data.ID | update "Response"}}" class="float-right btn btn-primary btn-sm"> <a id="update-response" href="{{.Data.ID | update "Response"}}" class="float-right btn btn-primary btn-sm">
<span class="fa fa-edit" aria-hidden="true"></span> <span class="fa fa-edit" aria-hidden="true"></span>
Vai alla gara! Vai alla gara!
</a> </a>