feat: add form response submition endpoint and forms filtering
Docker Build and Push / Build and Push Docker Image (push) Successful in 13m18s
Docker Build and Push / Build and Push Docker Image (push) Successful in 13m18s
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_response_answers_response_id;
|
||||||
|
DROP INDEX IF EXISTS idx_form_responses_form_id;
|
||||||
|
DROP TABLE IF EXISTS response_answers;
|
||||||
|
DROP TABLE IF EXISTS form_responses;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE form_responses (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
form_id UUID NOT NULL REFERENCES forms(id) ON DELETE RESTRICT,
|
||||||
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
submitted_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE response_answers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
response_id UUID NOT NULL REFERENCES form_responses(id) ON DELETE CASCADE,
|
||||||
|
question_id UUID NOT NULL,
|
||||||
|
form_id UUID NOT NULL,
|
||||||
|
answer_text TEXT,
|
||||||
|
FOREIGN KEY (form_id, question_id) REFERENCES questions(form_id, id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_form_responses_form_id ON form_responses(form_id);
|
||||||
|
CREATE INDEX idx_response_answers_response_id ON response_answers(response_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- name: CreateFormResponse :one
|
||||||
|
INSERT INTO form_responses (form_id, user_id)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetFormResponsesByFormID :many
|
||||||
|
SELECT * FROM form_responses
|
||||||
|
WHERE form_id = $1
|
||||||
|
ORDER BY submitted_at DESC;
|
||||||
|
|
||||||
|
-- name: FormHasResponses :one
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM form_responses WHERE form_id = $1
|
||||||
|
) AS has_responses;
|
||||||
|
|
||||||
|
-- name: CreateResponseAnswer :one
|
||||||
|
INSERT INTO response_answers (response_id, question_id, form_id, answer_text)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetAnswersByResponseID :many
|
||||||
|
SELECT * FROM response_answers
|
||||||
|
WHERE response_id = $1
|
||||||
|
ORDER BY id ASC;
|
||||||
@@ -69,6 +69,13 @@ type Form struct {
|
|||||||
UpdatedAt pgtype.Timestamptz
|
UpdatedAt pgtype.Timestamptz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FormResponse struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
FormID uuid.UUID
|
||||||
|
UserID *uuid.UUID
|
||||||
|
SubmittedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
type Question struct {
|
type Question struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
FormID uuid.UUID
|
FormID uuid.UUID
|
||||||
@@ -94,6 +101,14 @@ type RefreshToken struct {
|
|||||||
CreatedAt pgtype.Timestamptz
|
CreatedAt pgtype.Timestamptz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponseAnswer struct {
|
||||||
|
ID int64
|
||||||
|
ResponseID uuid.UUID
|
||||||
|
QuestionID uuid.UUID
|
||||||
|
FormID uuid.UUID
|
||||||
|
AnswerText pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Email string
|
Email string
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: responses.sql
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createFormResponse = `-- name: CreateFormResponse :one
|
||||||
|
INSERT INTO form_responses (form_id, user_id)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING id, form_id, user_id, submitted_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateFormResponseParams struct {
|
||||||
|
FormID uuid.UUID
|
||||||
|
UserID *uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateFormResponse(ctx context.Context, arg CreateFormResponseParams) (FormResponse, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createFormResponse, arg.FormID, arg.UserID)
|
||||||
|
var i FormResponse
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FormID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.SubmittedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResponseAnswer = `-- name: CreateResponseAnswer :one
|
||||||
|
INSERT INTO response_answers (response_id, question_id, form_id, answer_text)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING id, response_id, question_id, form_id, answer_text
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateResponseAnswerParams struct {
|
||||||
|
ResponseID uuid.UUID
|
||||||
|
QuestionID uuid.UUID
|
||||||
|
FormID uuid.UUID
|
||||||
|
AnswerText pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateResponseAnswer(ctx context.Context, arg CreateResponseAnswerParams) (ResponseAnswer, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createResponseAnswer,
|
||||||
|
arg.ResponseID,
|
||||||
|
arg.QuestionID,
|
||||||
|
arg.FormID,
|
||||||
|
arg.AnswerText,
|
||||||
|
)
|
||||||
|
var i ResponseAnswer
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ResponseID,
|
||||||
|
&i.QuestionID,
|
||||||
|
&i.FormID,
|
||||||
|
&i.AnswerText,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const formHasResponses = `-- name: FormHasResponses :one
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM form_responses WHERE form_id = $1
|
||||||
|
) AS has_responses
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) FormHasResponses(ctx context.Context, formID uuid.UUID) (bool, error) {
|
||||||
|
row := q.db.QueryRow(ctx, formHasResponses, formID)
|
||||||
|
var has_responses bool
|
||||||
|
err := row.Scan(&has_responses)
|
||||||
|
return has_responses, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAnswersByResponseID = `-- name: GetAnswersByResponseID :many
|
||||||
|
SELECT id, response_id, question_id, form_id, answer_text FROM response_answers
|
||||||
|
WHERE response_id = $1
|
||||||
|
ORDER BY id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAnswersByResponseID(ctx context.Context, responseID uuid.UUID) ([]ResponseAnswer, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getAnswersByResponseID, responseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ResponseAnswer
|
||||||
|
for rows.Next() {
|
||||||
|
var i ResponseAnswer
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ResponseID,
|
||||||
|
&i.QuestionID,
|
||||||
|
&i.FormID,
|
||||||
|
&i.AnswerText,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormResponsesByFormID = `-- name: GetFormResponsesByFormID :many
|
||||||
|
SELECT id, form_id, user_id, submitted_at FROM form_responses
|
||||||
|
WHERE form_id = $1
|
||||||
|
ORDER BY submitted_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetFormResponsesByFormID(ctx context.Context, formID uuid.UUID) ([]FormResponse, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getFormResponsesByFormID, formID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []FormResponse
|
||||||
|
for rows.Next() {
|
||||||
|
var i FormResponse
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FormID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.SubmittedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"ristek-task-be/internal/db/sqlc/repository"
|
"ristek-task-be/internal/db/sqlc/repository"
|
||||||
"ristek-task-be/internal/middleware"
|
"ristek-task-be/internal/middleware"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -142,6 +144,22 @@ func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q := r.URL.Query()
|
||||||
|
search := strings.TrimSpace(q.Get("search"))
|
||||||
|
status := strings.ToLower(strings.TrimSpace(q.Get("status")))
|
||||||
|
sortBy := strings.ToLower(strings.TrimSpace(q.Get("sort_by")))
|
||||||
|
sortDir := strings.ToLower(strings.TrimSpace(q.Get("sort_dir")))
|
||||||
|
|
||||||
|
if status != "has_responses" && status != "no_responses" {
|
||||||
|
status = ""
|
||||||
|
}
|
||||||
|
if sortBy != "updated_at" {
|
||||||
|
sortBy = "created_at"
|
||||||
|
}
|
||||||
|
if sortDir != "oldest" {
|
||||||
|
sortDir = "newest"
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -163,6 +181,16 @@ func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
items := make([]listItem, 0, len(forms))
|
items := make([]listItem, 0, len(forms))
|
||||||
for _, f := range forms {
|
for _, f := range forms {
|
||||||
|
if search != "" && !strings.Contains(strings.ToLower(f.Title), strings.ToLower(search)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if status == "has_responses" && f.ResponseCount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if status == "no_responses" && f.ResponseCount > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
desc := ""
|
desc := ""
|
||||||
if f.Description.Valid {
|
if f.Description.Valid {
|
||||||
desc = f.Description.String
|
desc = f.Description.String
|
||||||
@@ -177,6 +205,19 @@ func (h *Handler) FormsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
var ti, tj time.Time
|
||||||
|
if sortBy == "updated_at" {
|
||||||
|
ti, tj = items[i].UpdatedAt, items[j].UpdatedAt
|
||||||
|
} else {
|
||||||
|
ti, tj = items[i].CreatedAt, items[j].CreatedAt
|
||||||
|
}
|
||||||
|
if sortDir == "oldest" {
|
||||||
|
return ti.Before(tj)
|
||||||
|
}
|
||||||
|
return ti.After(tj)
|
||||||
|
})
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_ = json.NewEncoder(w).Encode(items)
|
_ = json.NewEncoder(w).Encode(items)
|
||||||
@@ -379,6 +420,18 @@ func (h *Handler) FormDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasResponses, err := h.repository.FormHasResponses(ctx, formID)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to check responses: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hasResponses {
|
||||||
|
w.WriteHeader(http.StatusConflict)
|
||||||
|
_, _ = w.Write([]byte("form already has responses and cannot be deleted"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.repository.DeleteForm(ctx, formID); err != nil {
|
if err := h.repository.DeleteForm(ctx, formID); err != nil {
|
||||||
internalServerError(w, err)
|
internalServerError(w, err)
|
||||||
log.Printf("failed to delete form: %s", err)
|
log.Printf("failed to delete form: %s", err)
|
||||||
@@ -433,3 +486,213 @@ func (h *Handler) saveQuestions(ctx context.Context, formID uuid.UUID, inputs []
|
|||||||
|
|
||||||
return questions, options, nil
|
return questions, options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AnswerInput struct {
|
||||||
|
QuestionID string `json:"question_id"`
|
||||||
|
Answer string `json:"answer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmitResponseRequest struct {
|
||||||
|
Answers []AnswerInput `json:"answers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnswerResponse struct {
|
||||||
|
QuestionID string `json:"question_id"`
|
||||||
|
Answer string `json:"answer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmitResponseResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
FormID string `json:"form_id"`
|
||||||
|
SubmittedAt string `json:"submitted_at"`
|
||||||
|
Answers []AnswerResponse `json:"answers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) FormResponsesPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
formID, err := uuid.Parse(r.PathValue("id"))
|
||||||
|
if err != nil {
|
||||||
|
badRequest(w, errors.New("invalid form id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req SubmitResponseRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
badRequest(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Answers) == 0 {
|
||||||
|
badRequest(w, errors.New("answers are required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
form, err := h.repository.GetFormByID(ctx, formID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = w.Write([]byte("form not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to get form: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
questions, err := h.repository.GetQuestionsByFormID(ctx, formID)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to get questions: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
questionMap := make(map[uuid.UUID]repository.Question, len(questions))
|
||||||
|
for _, q := range questions {
|
||||||
|
questionMap[q.ID] = q
|
||||||
|
}
|
||||||
|
|
||||||
|
answerMap := make(map[uuid.UUID]string, len(req.Answers))
|
||||||
|
for _, a := range req.Answers {
|
||||||
|
qid, err := uuid.Parse(a.QuestionID)
|
||||||
|
if err != nil {
|
||||||
|
badRequest(w, errors.New("invalid question_id: "+a.QuestionID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, exists := questionMap[qid]; !exists {
|
||||||
|
badRequest(w, errors.New("question not found in form: "+a.QuestionID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
answerMap[qid] = a.Answer
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, q := range questions {
|
||||||
|
if q.Required {
|
||||||
|
if ans, ok := answerMap[q.ID]; !ok || ans == "" {
|
||||||
|
badRequest(w, errors.New("required question not answered: "+q.Title))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userID *uuid.UUID
|
||||||
|
if uid, ok := h.currentUserID(r); ok {
|
||||||
|
userID = &uid
|
||||||
|
}
|
||||||
|
|
||||||
|
formResp, err := h.repository.CreateFormResponse(ctx, repository.CreateFormResponseParams{
|
||||||
|
FormID: form.ID,
|
||||||
|
UserID: userID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to create form response: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := h.repository.IncrementResponseCount(ctx, formID); err != nil {
|
||||||
|
log.Printf("failed to increment response count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var answerResponses []AnswerResponse
|
||||||
|
for qid, answerText := range answerMap {
|
||||||
|
_, err := h.repository.CreateResponseAnswer(ctx, repository.CreateResponseAnswerParams{
|
||||||
|
ResponseID: formResp.ID,
|
||||||
|
QuestionID: qid,
|
||||||
|
FormID: formID,
|
||||||
|
AnswerText: pgtype.Text{String: answerText, Valid: answerText != ""},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to create response answer: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
answerResponses = append(answerResponses, AnswerResponse{
|
||||||
|
QuestionID: qid.String(),
|
||||||
|
Answer: answerText,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if answerResponses == nil {
|
||||||
|
answerResponses = []AnswerResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
submittedAt := ""
|
||||||
|
if formResp.SubmittedAt.Valid {
|
||||||
|
submittedAt = formResp.SubmittedAt.Time.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_ = json.NewEncoder(w).Encode(SubmitResponseResponse{
|
||||||
|
ID: formResp.ID.String(),
|
||||||
|
FormID: formResp.FormID.String(),
|
||||||
|
SubmittedAt: submittedAt,
|
||||||
|
Answers: answerResponses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) FormResponsesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, ok := h.currentUserID(r)
|
||||||
|
if !ok {
|
||||||
|
unauthorized(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formID, err := uuid.Parse(r.PathValue("id"))
|
||||||
|
if err != nil {
|
||||||
|
badRequest(w, errors.New("invalid form id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
form, err := h.repository.GetFormByID(ctx, formID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
internalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.UserID != userID {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := h.repository.GetFormResponsesByFormID(ctx, formID)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, err)
|
||||||
|
log.Printf("failed to get responses: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SubmittedAt string `json:"submitted_at"`
|
||||||
|
UserID string `json:"user_id,omitempty"`
|
||||||
|
}
|
||||||
|
items := make([]responseItem, 0, len(responses))
|
||||||
|
for _, resp := range responses {
|
||||||
|
uid := ""
|
||||||
|
if resp.UserID != nil {
|
||||||
|
uid = resp.UserID.String()
|
||||||
|
}
|
||||||
|
sat := ""
|
||||||
|
if resp.SubmittedAt.Valid {
|
||||||
|
sat = resp.SubmittedAt.Time.String()
|
||||||
|
}
|
||||||
|
items = append(items, responseItem{
|
||||||
|
ID: resp.ID.String(),
|
||||||
|
SubmittedAt: sat,
|
||||||
|
UserID: uid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(w).Encode(items)
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ func router(repository *repository.Queries, jwt *jwt.JWT) *http.ServeMux {
|
|||||||
formRoute.Handle("PUT /{id}", middleware.Auth(jwt)(http.HandlerFunc(h.FormPut)))
|
formRoute.Handle("PUT /{id}", middleware.Auth(jwt)(http.HandlerFunc(h.FormPut)))
|
||||||
formRoute.Handle("DELETE /{id}", middleware.Auth(jwt)(http.HandlerFunc(h.FormDelete)))
|
formRoute.Handle("DELETE /{id}", middleware.Auth(jwt)(http.HandlerFunc(h.FormDelete)))
|
||||||
|
|
||||||
|
formRoute.HandleFunc("POST /{id}/response", h.FormResponsesPost)
|
||||||
|
formRoute.Handle("GET /{id}/responses", middleware.Auth(jwt)(http.HandlerFunc(h.FormResponsesGet)))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user