chore(deps): update module github.com/coreos/go-oidc to v3 - autoclosed #17
@ -52,6 +52,7 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</QuestionHeader>
|
||||
<slot></slot>
|
||||
<div class="card-body">
|
||||
{#if false && response_history}
|
||||
<div class="d-flex justify-content-end mb-2">
|
||||
@ -88,6 +89,7 @@
|
||||
{proposals}
|
||||
readonly
|
||||
bind:value={value}
|
||||
on:change={() => { dispatch("change"); }}
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
@ -103,17 +105,30 @@
|
||||
{proposals}
|
||||
{readonly}
|
||||
bind:value={value}
|
||||
on:change={() => { dispatch("change"); }}
|
||||
/>
|
||||
{/await}
|
||||
{:else if readonly}
|
||||
<p class="card-text alert alert-secondary" style="white-space: pre-line">{value}</p>
|
||||
{:else if question.kind == 'int'}
|
||||
<input class="ml-5 col-sm-2 form-control" type="number" bind:value={value} placeholder={question.placeholder}>
|
||||
<input
|
||||
class="ml-5 col-sm-2 form-control"
|
||||
type="number"
|
||||
bind:value={value}
|
||||
placeholder={question.placeholder}
|
||||
on:change={() => { dispatch("change"); }}
|
||||
>
|
||||
{:else}
|
||||
<textarea class="form-control" rows="6" bind:value={value} placeholder={question.placeholder}></textarea>
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="6"
|
||||
bind:value={value}
|
||||
placeholder={question.placeholder}
|
||||
on:change={() => { dispatch("change"); }}
|
||||
></textarea>
|
||||
{/if}
|
||||
|
||||
{#if survey.corrected}
|
||||
{#if survey && survey.corrected}
|
||||
<ResponseCorrected
|
||||
response={response_history}
|
||||
{survey}
|
||||
|
@ -1,21 +0,0 @@
|
||||
<script>
|
||||
export let proposal = null;
|
||||
export let kind = 'mcq';
|
||||
export let value;
|
||||
|
||||
let inputType = 'checkbox';
|
||||
$: {
|
||||
switch(kind) {
|
||||
case 'mcq':
|
||||
inputType = 'checkbox';
|
||||
break;
|
||||
default:
|
||||
inputType = 'radio';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
type={inputType}
|
||||
bind:value={value}
|
||||
{JSON.stringify(proposal)}
|
@ -1,4 +1,6 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import { QuestionProposal } from '../lib/questions';
|
||||
|
||||
export let edit = false;
|
||||
@ -16,6 +18,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function addProposal() {
|
||||
const p = new QuestionProposal();
|
||||
p.id_question = id_question;
|
||||
@ -35,7 +39,7 @@
|
||||
id={prefixid + 'p' + proposal.id}
|
||||
bind:group={valueCheck}
|
||||
value={proposal.id.toString()}
|
||||
on:change={() => { value = valueCheck.join(',')}}
|
||||
on:change={() => { value = valueCheck.join(','); dispatch("change"); }}
|
||||
>
|
||||
{:else}
|
||||
<input
|
||||
@ -46,6 +50,7 @@
|
||||
id={prefixid + 'p' + proposal.id}
|
||||
bind:group={value}
|
||||
value={proposal.id.toString()}
|
||||
on:change={() => { dispatch("change"); }}
|
||||
>
|
||||
{/if}
|
||||
{#if edit}
|
||||
|
@ -2,6 +2,8 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { getQuestions } from '../lib/questions';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
export let survey = null;
|
||||
|
||||
@ -60,6 +62,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3 text-sm-end">
|
||||
<label for="direct" class="col-form-label col-form-label-sm">Question en direct</label>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
{#await getQuestions(survey.id) then questions}
|
||||
<select id="direct" class="form-select form-select-sm" bind:value={survey.direct}>
|
||||
<option value={null}>Pas de direct</option>
|
||||
<option value={0}>Pause</option>
|
||||
{#each questions as question (question.id)}
|
||||
<option value={question.id}>{question.id} - {question.title}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3 text-sm-end">
|
||||
<label for="start_availability" class="col-form-label col-form-label-sm">Date de début</label>
|
||||
|
@ -4,7 +4,8 @@
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
{#if survey.startAvailability() > Date.now()}<span class="badge bg-info {className}">Prévu</span>>
|
||||
{#if survey.direct}<span class="badge bg-danger {className}">Direct</span>
|
||||
{:else if survey.startAvailability() > Date.now()}<span class="badge bg-info {className}">Prévu</span>
|
||||
{:else if survey.endAvailability() > Date.now()}<span class="badge bg-warning {className}">En cours</span>
|
||||
{:else if !survey.corrected}<span class="badge bg-primary text-light {className}">Terminé</span>
|
||||
{:else}<span class="badge bg-success {className}">Corrigé</span>
|
||||
|
@ -6,6 +6,17 @@
|
||||
import SurveyBadge from '../components/SurveyBadge.svelte';
|
||||
import { getSurveys } from '../lib/surveys';
|
||||
import { getScore } from '../lib/users';
|
||||
|
||||
let req_surveys = getSurveys();
|
||||
export let direct = null;
|
||||
|
||||
req_surveys.then((surveys) => {
|
||||
for (const survey of surveys) {
|
||||
if (survey.direct) {
|
||||
direct = survey;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<table class="table table-striped table-hover mb-0">
|
||||
@ -18,7 +29,7 @@
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
{#await getSurveys()}
|
||||
{#await req_surveys}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-3">
|
||||
<div class="spinner-border mx-3" role="status"></div>
|
||||
@ -36,7 +47,7 @@
|
||||
</th>
|
||||
</tr>
|
||||
{/if}
|
||||
<tr on:click={e => goto($user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}>
|
||||
<tr on:click={e => goto(survey.direct?`surveys/${survey.id}/live`:$user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}>
|
||||
<td>
|
||||
{survey.title}
|
||||
<SurveyBadge {survey} class="float-end" />
|
||||
|
@ -8,12 +8,13 @@ class Survey {
|
||||
}
|
||||
}
|
||||
|
||||
update({ id, title, promo, group, shown, corrected, start_availability, end_availability }) {
|
||||
update({ id, title, promo, group, shown, direct, corrected, start_availability, end_availability }) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.promo = promo;
|
||||
this.group = group;
|
||||
this.shown = shown;
|
||||
this.direct = direct;
|
||||
this.corrected = corrected;
|
||||
if (this.start_availability != start_availability) {
|
||||
this.start_availability = start_availability;
|
||||
|
@ -2,6 +2,8 @@
|
||||
import { user } from '../stores/user';
|
||||
import SurveyList from '../components/SurveyList.svelte';
|
||||
import ValidateSubmissions from '../components/ValidateSubmissions.svelte';
|
||||
|
||||
let direct = null;
|
||||
</script>
|
||||
|
||||
<div class="card bg-light">
|
||||
@ -20,6 +22,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if direct}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<strong>Rejoins le cours maintenant !</strong> Il y a actuellement un questionnaire en direct : {direct.title}. <a href="surveys/{direct.id}/live">Clique ici pour le rejoindre</a>.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<p class="lead">Tu as fait les rendus suivants :</p>
|
||||
</div>
|
||||
<div class="d-none d-md-block col-md-auto">
|
||||
@ -38,6 +46,6 @@
|
||||
Vous devez <a href="auth/CRI" target="_self">vous identifier</a> pour accéder au contenu.
|
||||
</p>
|
||||
{/if}
|
||||
<SurveyList />
|
||||
<SurveyList bind:direct={direct} />
|
||||
</div>
|
||||
</div>
|
||||
|
370
atsebayt/src/routes/surveys/[sid]/admin.svelte
Normal file
370
atsebayt/src/routes/surveys/[sid]/admin.svelte
Normal file
@ -0,0 +1,370 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { user } from '../../../stores/user';
|
||||
import SurveyAdmin from '../../../components/SurveyAdmin.svelte';
|
||||
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
||||
import { getQuestions } from '../../../lib/questions';
|
||||
import { getUsers } from '../../../lib/users';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
let survey;
|
||||
let req_questions;
|
||||
|
||||
surveyP.then((s) => {
|
||||
survey = s;
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
});
|
||||
|
||||
function updateQuestions() {
|
||||
req_questions = getQuestions(survey.id);
|
||||
}
|
||||
|
||||
let ws = null;
|
||||
let ws_up = false;
|
||||
let wsstats = null;
|
||||
let current_question = null;
|
||||
let responses = {};
|
||||
|
||||
let users = {};
|
||||
function updateUsers() {
|
||||
getUsers().then((usr) => {
|
||||
const tmp = { };
|
||||
for (const u of usr) {
|
||||
tmp[u.id.toString()] = u;
|
||||
}
|
||||
users = tmp;
|
||||
});
|
||||
}
|
||||
updateUsers();
|
||||
|
||||
let responsesbyid = { };
|
||||
$: {
|
||||
const tmp = { };
|
||||
for (const response in responses) {
|
||||
if (!tmp[response]) tmp[response] = [];
|
||||
for (const r in responses[response]) {
|
||||
tmp[response].push(responses[response][r]);
|
||||
}
|
||||
}
|
||||
responsesbyid = tmp;
|
||||
}
|
||||
|
||||
function wsconnect() {
|
||||
if (ws !== null) return;
|
||||
|
||||
ws = new WebSocket((window.location.protocol == 'https'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws-admin`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
ws.send('{"action":"get_responses"}');
|
||||
ws.send('{"action":"get_stats"}');
|
||||
});
|
||||
|
||||
ws.addEventListener("close", (e) => {
|
||||
ws_up = false;
|
||||
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
|
||||
ws = null;
|
||||
setTimeout(function() {
|
||||
wsconnect();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
ws.addEventListener("error", (err) => {
|
||||
ws_up = false;
|
||||
console.log('Socket closed due to error.', err.message);
|
||||
ws = null;
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (message) => {
|
||||
const data = JSON.parse(message.data);
|
||||
console.log(data);
|
||||
if (data.action && data.action == "new_question") {
|
||||
current_question = data.question;
|
||||
} else if (data.action && data.action == "stats") {
|
||||
wsstats = data.stats;
|
||||
} else if (data.action && data.action == "new_response") {
|
||||
if (!responses[data.question]) responses[data.question] = {};
|
||||
responses[data.question][data.user] = data.value;
|
||||
} else {
|
||||
current_question = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
{#if survey.direct !== null}
|
||||
<a href="surveys/{survey.id}/live" class="btn btn-danger ms-1 float-end" title="Aller au direct"><i class="bi bi-film"></i></a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary ms-1 float-end"
|
||||
on:click={() => { if (confirm("Sûr ?")) ws.send('{"action":"end"}') }}
|
||||
>
|
||||
Terminer
|
||||
</button>
|
||||
{/if}
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">
|
||||
Administration
|
||||
</small>
|
||||
</h2>
|
||||
{#if survey.direct !== null}
|
||||
<div
|
||||
class="badge rounded-pill ms-2"
|
||||
class:bg-success={ws_up}
|
||||
class:bg-danger={!ws_up}
|
||||
>
|
||||
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<SurveyBadge
|
||||
class="mx-2"
|
||||
{survey}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if survey.direct === null}
|
||||
<SurveyAdmin
|
||||
{survey}
|
||||
/>
|
||||
{:else}
|
||||
{#await req_questions}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<div class="card my-3">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Question
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1"
|
||||
on:click={updateQuestions}
|
||||
title="Rafraîchir les questions"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
Réponses
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
disabled={!current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"pause"}')} }
|
||||
>
|
||||
Pause
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each questions as question (question.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
<a href="surveys/{sid}/admin#q{question.id}_res">
|
||||
{question.title}
|
||||
</a>
|
||||
{:else}
|
||||
{question.title}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
{Object.keys(responses[question.id]).length}
|
||||
{:else}
|
||||
0
|
||||
{/if}
|
||||
{#if wsstats}/ {wsstats.nb_clients}{/if}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
disabled={question.id === current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"new_question", "question":' + question.id + '}')} }
|
||||
>
|
||||
Lancer cette question
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_responses"}') }}
|
||||
title="Rafraîchir les réponses"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-card-checklist"></i>
|
||||
</button>
|
||||
<h3>
|
||||
Réponses
|
||||
</h3>
|
||||
{#if Object.keys(responses).length}
|
||||
{#each Object.keys(responses) as q, qid (qid)}
|
||||
{#await req_questions then questions}
|
||||
{#each questions as question}
|
||||
{#if question.id == q}
|
||||
<h4 id="q{question.id}_res">
|
||||
{question.title}
|
||||
</h4>
|
||||
{#if question.kind == 'ucq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.kind == 'mcq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else}
|
||||
<div class="card mb-4">
|
||||
<ul class="list-group list-group-flush">
|
||||
{#each Object.keys(responses[q]) as user, rid (rid)}
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{responses[q][user]}
|
||||
</span>
|
||||
<a href="users/{user}" target="_blank" class="badge bg-dark rounded-pill">
|
||||
{#if users && users[user]}
|
||||
{users[user].login}
|
||||
{:else}
|
||||
{user}
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_stats"}') }}
|
||||
title="Rafraîchir les stats"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-123"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary ms-1 float-end"
|
||||
title="Rafraîchir la liste des utilisateurs"
|
||||
on:click={updateUsers}
|
||||
>
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
<i class="bi bi-people"></i>
|
||||
</button>
|
||||
<h3>
|
||||
Connectés
|
||||
{#if wsstats}
|
||||
<small class="text-muted">{wsstats.nb_clients} utilisateurs</small>
|
||||
{/if}
|
||||
</h3>
|
||||
{#if wsstats}
|
||||
<div class="row row-cols-5 py-3">
|
||||
{#each wsstats.users as login, lid (lid)}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<img alt="{login}" src="//photos.cri.epita.fr/thumb/{login}" class="card-img-top">
|
||||
<div class="card-footer text-center text-truncate p-0">
|
||||
<a href="users/{login}" target="_blank">
|
||||
{login}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/await}
|
@ -11,6 +11,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { user } from '../../../stores/user';
|
||||
import SurveyAdmin from '../../../components/SurveyAdmin.svelte';
|
||||
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
||||
@ -20,13 +22,26 @@
|
||||
|
||||
export let surveyP;
|
||||
|
||||
$: {
|
||||
if (surveyP) {
|
||||
surveyP.then((survey) => {
|
||||
if (survey.direct && !$user.is_admin) {
|
||||
goto(`surveys/${survey.id}/live`);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let edit = false;
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer" ng-if=" && !edit"><i class="bi bi-pencil"></i></button>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses" ng-if="user.is_admin"><i class="bi bi-files"></i></a>
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{#if survey.direct}
|
||||
<a href="surveys/{survey.id}/live" class="btn btn-danger ms-1 float-end" title="Aller au direct"><i class="bi bi-film"></i></a>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
|
118
atsebayt/src/routes/surveys/[sid]/live.svelte
Normal file
118
atsebayt/src/routes/surveys/[sid]/live.svelte
Normal file
@ -0,0 +1,118 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { user } from '../../../stores/user';
|
||||
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
||||
import QuestionForm from '../../../components/QuestionForm.svelte';
|
||||
import { getQuestion } from '../../../lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
let survey;
|
||||
|
||||
surveyP.then((s) => survey = s);
|
||||
|
||||
let ws_up = false;
|
||||
let show_question = null;
|
||||
let value;
|
||||
|
||||
function wsconnect() {
|
||||
const ws = new WebSocket((window.location.protocol == 'https'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
});
|
||||
|
||||
ws.addEventListener("close", (e) => {
|
||||
ws_up = false;
|
||||
show_question = false;
|
||||
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
|
||||
setTimeout(function() {
|
||||
wsconnect();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
ws.addEventListener("error", (err) => {
|
||||
ws_up = false;
|
||||
console.log('Socket closed due to error.', err.message);
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (message) => {
|
||||
const data = JSON.parse(message.data);
|
||||
console.log(data);
|
||||
if (data.action && data.action == "new_question") {
|
||||
show_question = data.question;
|
||||
} else {
|
||||
show_question = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
wsconnect();
|
||||
|
||||
function sendValue() {
|
||||
if (show_question && value) {
|
||||
survey.submitAnswers([{"id_question": show_question, "value": value}], $user.id_user).then((response) => {
|
||||
console.log("Vos réponses ont bien étés sauvegardées.");
|
||||
}, (error) => {
|
||||
value = null;
|
||||
console.log("Une erreur s'est produite durant l'envoi de vos réponses : " + error + "<br>Veuillez réessayer dans quelques instants.");
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
<a href="surveys/{survey.id}/admin" class="btn btn-primary ms-1 float-end" title="Aller à l'interface d'administration"><i class="bi bi-pencil"></i></a>
|
||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center mb-5">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
</h2>
|
||||
<div
|
||||
class="badge rounded-pill ms-2"
|
||||
class:bg-success={ws_up}
|
||||
class:bg-danger={!ws_up}
|
||||
>
|
||||
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={sendValue}>
|
||||
{#if show_question}
|
||||
{#await getQuestion(show_question)}
|
||||
Please wait
|
||||
{:then question}
|
||||
<QuestionForm
|
||||
qid={show_question}
|
||||
{question}
|
||||
bind:value={value}
|
||||
on:change={sendValue}
|
||||
>
|
||||
<!--div class="progress" style="border-radius: 0; height: 4px">
|
||||
<div class="progress-bar" role="progressbar" style="width: 25%"></div>
|
||||
</div-->
|
||||
</QuestionForm>
|
||||
{/await}
|
||||
{:else if ws_up}
|
||||
<h2 class="text-center">
|
||||
Pas de question actuellement
|
||||
</h2>
|
||||
{:else}
|
||||
<h2 class="text-center">
|
||||
La session est terminée. <small class="text-muted">On se retrouve une prochaine fois…</small>
|
||||
</h2>
|
||||
{/if}
|
||||
</form>
|
||||
{/await}
|
1
db.go
1
db.go
@ -83,6 +83,7 @@ CREATE TABLE IF NOT EXISTS surveys(
|
||||
title VARCHAR(255),
|
||||
promo MEDIUMINT NOT NULL,
|
||||
shown BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
direct INTEGER DEFAULT NULL,
|
||||
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
|
358
direct.go
Normal file
358
direct.go
Normal file
@ -0,0 +1,358 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"nhooyr.io/websocket"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
)
|
||||
|
||||
var (
|
||||
WSClients = map[int64][]WSClient{}
|
||||
WSClientsMutex = sync.RWMutex{}
|
||||
WSAdmin = []WSClient{}
|
||||
WSAdminMutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/surveys/:sid/ws", rawAuthHandler(SurveyWS, loggedUser))
|
||||
router.GET("/api/surveys/:sid/ws-admin", rawAuthHandler(SurveyWSAdmin, adminRestricted))
|
||||
|
||||
router.GET("/api/surveys/:sid/ws/stats", apiHandler(surveyHandler(func(s Survey, body []byte) HTTPResponse {
|
||||
return APIResponse{
|
||||
WSSurveyStats(s.Id),
|
||||
}
|
||||
}), adminRestricted))
|
||||
}
|
||||
|
||||
func WSSurveyStats(sid int64) map[string]interface{} {
|
||||
var users []string
|
||||
var nb int
|
||||
|
||||
WSClientsMutex.RLock()
|
||||
defer WSClientsMutex.RUnlock()
|
||||
if w, ok := WSClients[sid]; ok {
|
||||
nb = len(w)
|
||||
for _, ws := range w {
|
||||
users = append(users, ws.u.Login)
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"nb_clients": nb,
|
||||
"users": users,
|
||||
}
|
||||
}
|
||||
|
||||
type WSClient struct {
|
||||
ws *websocket.Conn
|
||||
c chan WSMessage
|
||||
u *User
|
||||
sid int64
|
||||
}
|
||||
|
||||
func SurveyWS_run(ws *websocket.Conn, c chan WSMessage, sid int64, u *User) {
|
||||
for {
|
||||
msg, ok := <-c
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := wsjson.Write(ctx, ws, msg)
|
||||
if err != nil {
|
||||
log.Println("Error on WebSocket:", err)
|
||||
ws.Close(websocket.StatusInternalError, "error on write")
|
||||
break
|
||||
}
|
||||
}
|
||||
ws.Close(websocket.StatusNormalClosure, "end")
|
||||
|
||||
WSClientsMutex.Lock()
|
||||
defer WSClientsMutex.Unlock()
|
||||
|
||||
for i, clt := range WSClients[sid] {
|
||||
if clt.ws == ws {
|
||||
WSClients[sid][i] = WSClients[sid][len(WSClients[sid])-1]
|
||||
WSClients[sid] = WSClients[sid][:len(WSClients[sid])-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Println(u.Login, "disconnected")
|
||||
}
|
||||
|
||||
func msgCurrentState(survey *Survey) (msg WSMessage) {
|
||||
if *survey.Direct == 0 {
|
||||
msg = WSMessage{
|
||||
Action: "pause",
|
||||
}
|
||||
} else {
|
||||
msg = WSMessage{
|
||||
Action: "new_question",
|
||||
QuestionId: survey.Direct,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SurveyWS(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound)
|
||||
return
|
||||
} else if survey.Direct == nil {
|
||||
http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
ws, err := websocket.Accept(w, r, nil)
|
||||
if err != nil {
|
||||
log.Fatal("error get connection", err)
|
||||
}
|
||||
|
||||
log.Println(u.Login, "is now connected to WS", sid)
|
||||
|
||||
c := make(chan WSMessage, 1)
|
||||
|
||||
WSClientsMutex.Lock()
|
||||
defer WSClientsMutex.Unlock()
|
||||
WSClients[survey.Id] = append(WSClients[survey.Id], WSClient{ws, c, u, survey.Id})
|
||||
|
||||
// Send current state
|
||||
c <- msgCurrentState(&survey)
|
||||
|
||||
go SurveyWS_run(ws, c, survey.Id, u)
|
||||
}
|
||||
}
|
||||
|
||||
func WSWriteAll(message WSMessage) {
|
||||
WSClientsMutex.RLock()
|
||||
defer WSClientsMutex.RUnlock()
|
||||
|
||||
for _, wss := range WSClients {
|
||||
for _, ws := range wss {
|
||||
ws.c <- message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WSMessage struct {
|
||||
Action string `json:"action"`
|
||||
SurveyId *int64 `json:"survey,omitempty"`
|
||||
QuestionId *int64 `json:"question,omitempty"`
|
||||
Stats map[string]interface{} `json:"stats,omitempty"`
|
||||
UserId *int64 `json:"user,omitempty"`
|
||||
Response string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Survey) WSWriteAll(message WSMessage) {
|
||||
WSClientsMutex.RLock()
|
||||
defer WSClientsMutex.RUnlock()
|
||||
|
||||
if wss, ok := WSClients[s.Id]; ok {
|
||||
for _, ws := range wss {
|
||||
ws.c <- message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) WSCloseAll(message string) {
|
||||
WSClientsMutex.RLock()
|
||||
defer WSClientsMutex.RUnlock()
|
||||
|
||||
if wss, ok := WSClients[s.Id]; ok {
|
||||
for _, ws := range wss {
|
||||
close(ws.c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Admin //////////////////////////////////////////////////////////////
|
||||
|
||||
func SurveyWSAdmin_run(ctx context.Context, ws *websocket.Conn, c chan WSMessage, sid int64, u *User) {
|
||||
ct := time.Tick(25000 * time.Millisecond)
|
||||
loopadmin:
|
||||
for {
|
||||
select {
|
||||
case <-ct:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := wsjson.Write(ctx, ws, WSMessage{
|
||||
Action: "stats",
|
||||
Stats: WSSurveyStats(sid),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Error on WebSocket:", err)
|
||||
ws.Close(websocket.StatusInternalError, "error on write")
|
||||
break
|
||||
}
|
||||
|
||||
case msg, ok := <-c:
|
||||
if !ok {
|
||||
break loopadmin
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := wsjson.Write(ctx, ws, msg)
|
||||
if err != nil {
|
||||
log.Println("Error on WebSocket:", err)
|
||||
ws.Close(websocket.StatusInternalError, "error on write")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.Close(websocket.StatusNormalClosure, "end")
|
||||
|
||||
WSAdminMutex.Lock()
|
||||
defer WSAdminMutex.Unlock()
|
||||
|
||||
for i, clt := range WSAdmin {
|
||||
if clt.ws == ws {
|
||||
WSAdmin[i] = WSAdmin[len(WSAdmin)-1]
|
||||
WSAdmin = WSAdmin[:len(WSAdmin)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Println(u.Login, "admin disconnected")
|
||||
}
|
||||
|
||||
func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound)
|
||||
return
|
||||
} else if survey.Direct == nil {
|
||||
http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
ws, err := websocket.Accept(w, r, nil)
|
||||
if err != nil {
|
||||
log.Fatal("error get connection", err)
|
||||
}
|
||||
|
||||
log.Println(u.Login, "is now connected to WS-admin", sid)
|
||||
|
||||
c := make(chan WSMessage, 2)
|
||||
|
||||
WSAdminMutex.Lock()
|
||||
defer WSAdminMutex.Unlock()
|
||||
WSAdmin = append(WSAdmin, WSClient{ws, c, u, survey.Id})
|
||||
|
||||
// Send current state
|
||||
c <- msgCurrentState(&survey)
|
||||
|
||||
go SurveyWSAdmin_run(r.Context(), ws, c, survey.Id, u)
|
||||
go func(c chan WSMessage, sid int) {
|
||||
var v WSMessage
|
||||
var err error
|
||||
for {
|
||||
err = wsjson.Read(context.Background(), ws, &v)
|
||||
if err != nil {
|
||||
log.Println("Error when receiving message:", err)
|
||||
close(c)
|
||||
break
|
||||
}
|
||||
|
||||
if v.Action == "new_question" && v.QuestionId != nil {
|
||||
if survey, err := getSurvey(sid); err != nil {
|
||||
log.Println("Unable to retrieve survey:", err)
|
||||
} else {
|
||||
survey.Direct = v.QuestionId
|
||||
_, err = survey.Update()
|
||||
if err != nil {
|
||||
log.Println("Unable to update survey:", err)
|
||||
}
|
||||
|
||||
survey.WSWriteAll(v)
|
||||
v.SurveyId = &survey.Id
|
||||
WSAdminWriteAll(v)
|
||||
}
|
||||
} else if v.Action == "pause" {
|
||||
if survey, err := getSurvey(sid); err != nil {
|
||||
log.Println("Unable to retrieve survey:", err)
|
||||
} else {
|
||||
var u int64 = 0
|
||||
survey.Direct = &u
|
||||
_, err = survey.Update()
|
||||
if err != nil {
|
||||
log.Println("Unable to update survey:", err)
|
||||
}
|
||||
|
||||
survey.WSWriteAll(v)
|
||||
v.SurveyId = &survey.Id
|
||||
WSAdminWriteAll(v)
|
||||
}
|
||||
} else if v.Action == "end" {
|
||||
if survey, err := getSurvey(sid); err != nil {
|
||||
log.Println("Unable to retrieve survey:", err)
|
||||
} else {
|
||||
survey.Direct = nil
|
||||
_, err = survey.Update()
|
||||
if err != nil {
|
||||
log.Println("Unable to update survey:", err)
|
||||
}
|
||||
|
||||
survey.WSCloseAll("Fin du live")
|
||||
v.SurveyId = &survey.Id
|
||||
WSAdminWriteAll(v)
|
||||
}
|
||||
} else if v.Action == "get_stats" {
|
||||
err = wsjson.Write(context.Background(), ws, WSMessage{Action: "stats", Stats: WSSurveyStats(int64(sid))})
|
||||
} else if v.Action == "get_responses" {
|
||||
if survey, err := getSurvey(sid); err != nil {
|
||||
log.Println("Unable to retrieve survey:", err)
|
||||
} else if questions, err := survey.GetQuestions(); err != nil {
|
||||
log.Println("Unable to retrieve questions:", err)
|
||||
} else {
|
||||
for _, q := range questions {
|
||||
if responses, err := q.GetResponses(); err != nil {
|
||||
log.Println("Unable to retrieve questions:", err)
|
||||
} else {
|
||||
for _, r := range responses {
|
||||
wsjson.Write(context.Background(), ws, WSMessage{Action: "new_response", UserId: &r.IdUser, QuestionId: &q.Id, Response: r.Answer})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Println("Unknown admin action:", v.Action)
|
||||
}
|
||||
}
|
||||
}(c, sid)
|
||||
}
|
||||
}
|
||||
|
||||
func WSAdminWriteAll(message WSMessage) {
|
||||
WSAdminMutex.RLock()
|
||||
defer WSAdminMutex.RUnlock()
|
||||
|
||||
for _, ws := range WSAdmin {
|
||||
ws.c <- message
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) WSAdminWriteAll(message WSMessage) {
|
||||
WSAdminMutex.RLock()
|
||||
defer WSAdminMutex.RUnlock()
|
||||
|
||||
for _, ws := range WSAdmin {
|
||||
log.Println("snd", message, ws.sid, s.Id)
|
||||
if ws.sid == s.Id {
|
||||
ws.c <- message
|
||||
}
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
@ -15,4 +15,5 @@ require (
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
26
go.sum
26
go.sum
@ -43,15 +43,25 @@ github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjs
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -90,6 +100,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@ -104,6 +115,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@ -120,16 +132,23 @@ github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJz
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
||||
@ -139,9 +158,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -257,6 +279,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -411,6 +434,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -420,6 +444,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -76,6 +76,12 @@ func eraseCookie(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawAuthHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, _ *User, body []byte) {
|
||||
f(w, r, ps, body)
|
||||
}, access...)
|
||||
}
|
||||
|
||||
func rawAuthHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, *User, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
||||
r.RemoteAddr = addr
|
||||
@ -136,7 +142,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
|
||||
}
|
||||
}
|
||||
|
||||
f(w, r, ps, body)
|
||||
f(w, r, ps, user, body)
|
||||
}
|
||||
}
|
||||
|
||||
|
17
questions.go
17
questions.go
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -38,10 +39,18 @@ func init() {
|
||||
|
||||
return formatApiResponse(s.NewQuestion(new.Title, new.DescriptionRaw, new.Placeholder, new.Kind))
|
||||
}), adminRestricted))
|
||||
router.GET("/api/questions/:qid", apiHandler(questionHandler(
|
||||
func(s Question, _ []byte) HTTPResponse {
|
||||
return APIResponse{s}
|
||||
}), adminRestricted))
|
||||
router.GET("/api/questions/:qid", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
if u.IsAdmin {
|
||||
return APIResponse{q}
|
||||
} else if s, err := getSurvey(int(q.IdSurvey)); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if s.Shown || (s.Direct != nil && *s.Direct == q.Id) {
|
||||
return APIResponse{q}
|
||||
} else {
|
||||
return APIErrorResponse{err: fmt.Errorf("Not authorized"), status: http.StatusForbidden}
|
||||
}
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(
|
||||
func(s Question, _ []byte) HTTPResponse {
|
||||
return APIResponse{s}
|
||||
|
@ -25,10 +25,16 @@ func init() {
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
if len(response.Answer) > 0 {
|
||||
if !s.Shown && (s.Direct == nil || *s.Direct != response.IdQuestion) {
|
||||
return APIErrorResponse{err: fmt.Errorf("Cette question n'est pas disponible")}
|
||||
} else if len(response.Answer) > 0 {
|
||||
if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
if s.Direct != nil {
|
||||
s.WSAdminWriteAll(WSMessage{Action: "new_response", UserId: &u.Id, QuestionId: &response.IdQuestion, Response: response.Answer})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
37
surveys.go
37
surveys.go
@ -20,11 +20,11 @@ func init() {
|
||||
router.GET("/api/surveys", apiAuthHandler(
|
||||
func(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
if u == nil {
|
||||
return formatApiResponse(getSurveys(fmt.Sprintf("WHERE shown = TRUE AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo)))
|
||||
return formatApiResponse(getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo)))
|
||||
} else if u.IsAdmin {
|
||||
return formatApiResponse(getSurveys("ORDER BY promo DESC, start_availability ASC"))
|
||||
} else {
|
||||
surveys, err := getSurveys(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
surveys, err := getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
@ -49,7 +49,7 @@ func init() {
|
||||
new.Promo = currentPromo
|
||||
}
|
||||
|
||||
return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.StartAvailability, new.EndAvailability))
|
||||
return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability))
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
@ -69,6 +69,22 @@ func init() {
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
|
||||
if new.Direct != current.Direct {
|
||||
if new.Direct == nil {
|
||||
current.WSCloseAll("")
|
||||
} else if *new.Direct == 0 {
|
||||
current.WSWriteAll(WSMessage{
|
||||
Action: "pause",
|
||||
})
|
||||
} else {
|
||||
current.WSWriteAll(WSMessage{
|
||||
Action: "new_question",
|
||||
QuestionId: new.Direct,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid", apiHandler(surveyHandler(
|
||||
@ -144,20 +160,21 @@ type Survey struct {
|
||||
Promo uint `json:"promo"`
|
||||
Group string `json:"group"`
|
||||
Shown bool `json:"shown"`
|
||||
Direct *int64 `json:"direct"`
|
||||
Corrected bool `json:"corrected"`
|
||||
StartAvailability time.Time `json:"start_availability"`
|
||||
EndAvailability time.Time `json:"end_availability"`
|
||||
}
|
||||
|
||||
func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil {
|
||||
if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var s Survey
|
||||
if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||
if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||
return
|
||||
}
|
||||
surveys = append(surveys, s)
|
||||
@ -171,17 +188,17 @@ func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error)
|
||||
}
|
||||
|
||||
func getSurvey(id int) (s Survey, err error) {
|
||||
err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
|
||||
err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
|
||||
return
|
||||
}
|
||||
|
||||
func NewSurvey(title string, promo uint, group string, shown bool, startAvailability time.Time, endAvailability time.Time) (*Survey, error) {
|
||||
if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?)", title, promo, group, shown, startAvailability, endAvailability); err != nil {
|
||||
func NewSurvey(title string, promo uint, group string, shown bool, direct *int64, startAvailability time.Time, endAvailability time.Time) (*Survey, error) {
|
||||
if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, direct, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?)", title, promo, group, shown, direct, startAvailability, endAvailability); err != nil {
|
||||
return nil, err
|
||||
} else if sid, err := res.LastInsertId(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Survey{sid, title, promo, group, shown, false, startAvailability, endAvailability}, nil
|
||||
return &Survey{sid, title, promo, group, shown, direct, false, startAvailability, endAvailability}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +244,7 @@ func (s Survey) GetScores() (scores map[int64]*float64, err error) {
|
||||
}
|
||||
|
||||
func (s *Survey) Update() (*Survey, error) {
|
||||
if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
||||
if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, direct = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Direct, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return s, err
|
||||
|
Reference in New Issue
Block a user