* fix

* update requests

* fixes

* ssl

* fix test

* remove pgbouncer

* cleanup
This commit is contained in:
Axel Adlercreutz
2025-04-11 11:29:54 +03:00
committed by GitHub
parent 6cfe6e6782
commit 8d106964f4
26 changed files with 333 additions and 19 deletions
+5 -1
View File
@@ -18,12 +18,16 @@ ENV SESSION_SECRET=secret \
DB_NAME=postgres
RUN apk update && \
apk add --no-cache wget && \
wget https://github.com/peterldowns/pgmigrate/releases/download/v0.0.6%2Bcommit.4f90829/pgmigrate-linux-amd64 -O /usr/local/bin/pgmigrate && \
chmod +x /usr/local/bin/pgmigrate
# Copy package files from gradesa
COPY gradesa/package*.json ./
# Install ALL dependencies (including dev) for build
RUN npm ci
RUN npm i
# Copy app files including .env.production
COPY gradesa/ .
+20 -2
View File
@@ -3,11 +3,29 @@
set -euo pipefail
cd "$(dirname "$0")"
echo $(pwd)
echo "Migrating"
pgmigrate migrate -d postgres://ohtu:password@localhost:7742/gradesa -m migrations
# Get database connection details from environment variables if available, otherwise use defaults
DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-7742}
DB_USER=${DB_USER:-ohtu}
DB_PASSWORD=${DB_PASSWORD:-password}
DB_NAME=${DB_NAME:-gradesa}
# Construct the database connection string
DB_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
# Run the migration
pgmigrate migrate -d "$DB_URL" -m migrations
echo "Migrating done"
# Check if --skip-schema-gen flag is provided
if [[ "$*" == *--skip-schema-gen* ]]; then
echo "Skipping schema generation"
exit 0
fi
# We generate the schema file to make sure that the schema is up to date
# The github workflow will check for uncommitted changes and fail if there is
# a difference between the schema file and the actual schema in the database
@@ -5,6 +5,6 @@ set -euo pipefail
cd "$(dirname "$0")"
psql "postgres://ohtu:password@localhost:7742/gradesa" -c "DROP SCHEMA public CASCADE;CREATE SCHEMA public;"
cd ../
cd ../../
npm run db:migrate
echo "Migration DONE"
+2 -2
View File
@@ -6,7 +6,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"start": "sh ./data/migrate.sh --skip-schema-gen && next start",
"lint": "next lint",
"test": "vitest --disable-console-intercept "
},
@@ -46,4 +46,4 @@
"ts-node": "^10.9.2",
"vitest": "^3.0.4"
}
}
}
+299
View File
@@ -0,0 +1,299 @@
CREATE OR REPLACE FUNCTION public.updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$function$
;
CREATE TABLE public.click_answers (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
user_id bigint NOT NULL,
click_exercise_id bigint NOT NULL,
answer text[] NOT NULL,
target_words text[] NOT NULL,
is_correct boolean NOT NULL GENERATED ALWAYS AS ((answer = target_words)) STORED
);
CREATE TRIGGER updated_at BEFORE UPDATE ON public.click_answers FOR EACH ROW EXECUTE FUNCTION updated_at();
CREATE TABLE public.click_exercises (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
title text NOT NULL,
category text NOT NULL,
target_words text[] NOT NULL,
all_words text[] NOT NULL
);
ALTER TABLE public.click_exercises
ADD CONSTRAINT click_exercises_all_words_target_words_key
UNIQUE (all_words, target_words);
ALTER TABLE public.click_exercises
ADD CONSTRAINT click_exercises_id_target_words_key
UNIQUE (id, target_words);
ALTER TABLE public.click_answers
ADD CONSTRAINT click_answers_click_exercise_id_fkey
FOREIGN KEY (click_exercise_id) REFERENCES click_exercises(id) ON DELETE CASCADE;
ALTER TABLE public.click_answers
ADD CONSTRAINT click_answers_click_exercise_id_target_words_fkey
FOREIGN KEY (click_exercise_id, target_words) REFERENCES click_exercises(id, target_words) ON UPDATE CASCADE ON DELETE CASCADE;
CREATE TABLE public.users (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
email text UNIQUE NOT NULL,
password_hash text NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
salt text NOT NULL,
username text NOT NULL DEFAULT ''::text,
is_admin boolean DEFAULT false
);
ALTER TABLE public.click_answers
ADD CONSTRAINT click_answers_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
CREATE TABLE public.click_to_exercises (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
click_id bigint,
exercise_id bigint
);
ALTER TABLE public.click_to_exercises
ADD CONSTRAINT click_to_exercises_click_id_fkey
FOREIGN KEY (click_id) REFERENCES click_exercises(id);
CREATE TABLE public.exercises (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now()
);
ALTER TABLE public.click_to_exercises
ADD CONSTRAINT click_to_exercises_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id);
CREATE TABLE public.draggable_words (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
word text NOT NULL,
correct_answer text NOT NULL
);
ALTER TABLE public.draggable_words
ADD CONSTRAINT draggable_words_id_correct_answer_key
UNIQUE (id, correct_answer);
ALTER TABLE public.draggable_words
ADD CONSTRAINT draggable_words_word_correct_answer_key
UNIQUE (word, correct_answer);
CREATE TABLE public.exercises_to_grammar_items (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
grammar_item_id bigint,
exercise_id bigint
);
ALTER TABLE public.exercises_to_grammar_items
ADD CONSTRAINT exercises_to_grammar_items_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id);
CREATE TABLE public.grammar_items (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
grammar_item text UNIQUE NOT NULL
);
ALTER TABLE public.exercises_to_grammar_items
ADD CONSTRAINT exercises_to_grammar_items_grammar_item_id_fkey
FOREIGN KEY (grammar_item_id) REFERENCES grammar_items(id);
CREATE SEQUENCE public.feedbacks_id_seq;
CREATE TABLE public.feedbacks (
id integer PRIMARY KEY NOT NULL DEFAULT nextval('feedbacks_id_seq'::regclass),
email character varying(255) NOT NULL,
message text NOT NULL,
user_id integer NOT NULL,
timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP
);
ALTER SEQUENCE public.feedbacks_id_seq OWNED BY public.feedbacks.id;
CREATE TABLE public.form_parts (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
form_id bigint NOT NULL,
title_en text,
title_de text,
step_label text NOT NULL,
advice_en text,
advice_de text,
advice_threshold numeric(5,2) DEFAULT 3
);
ALTER TABLE public.form_parts
ADD CONSTRAINT check_step_label
CHECK ((step_label ~ '^[A-Za-z0-9]$'::text));
ALTER TABLE public.form_parts
ADD CONSTRAINT unique_step_label
UNIQUE (form_id, step_label);
CREATE TABLE public.forms (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
public_id text UNIQUE NOT NULL,
title_en text,
title_de text,
description_en text,
description_de text
);
ALTER TABLE public.form_parts
ADD CONSTRAINT form_parts_form_id_fkey
FOREIGN KEY (form_id) REFERENCES forms(id) ON DELETE RESTRICT;
CREATE TABLE public.free_form_answers (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
free_form_exercise_id bigint NOT NULL,
answer text NOT NULL,
is_correct boolean DEFAULT false,
feedback text
);
CREATE INDEX free_form_answers_free_form_exercise_id_idx ON public.free_form_answers USING btree (free_form_exercise_id);
CREATE TABLE public.free_form_exercises (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
exercise_id bigint NOT NULL,
question text NOT NULL
);
ALTER TABLE public.free_form_answers
ADD CONSTRAINT free_form_answers_free_form_exercise_id_fkey
FOREIGN KEY (free_form_exercise_id) REFERENCES free_form_exercises(id) ON DELETE CASCADE;
ALTER TABLE public.free_form_exercises
ADD CONSTRAINT free_form_exercises_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE;
CREATE TABLE public.free_form_user_answers (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
answer text NOT NULL,
free_form_exercise_id bigint NOT NULL,
is_correct boolean DEFAULT false,
user_id bigint NOT NULL
);
ALTER TABLE public.free_form_user_answers
ADD CONSTRAINT free_form_user_answers_free_form_exercise_id_user_id_key
UNIQUE (free_form_exercise_id, user_id);
ALTER TABLE public.free_form_user_answers
ADD CONSTRAINT free_form_user_answers_free_form_exercise_id_fkey
FOREIGN KEY (free_form_exercise_id) REFERENCES free_form_exercises(id) ON DELETE CASCADE;
ALTER TABLE public.free_form_user_answers
ADD CONSTRAINT free_form_user_answers_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
CREATE TABLE public.part_questions (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
form_part_id bigint NOT NULL,
title_en text,
title_de text
);
ALTER TABLE public.part_questions
ADD CONSTRAINT part_questions_form_part_id_fkey
FOREIGN KEY (form_part_id) REFERENCES form_parts(id) ON DELETE RESTRICT;
CREATE TABLE public.pgmigrate_migrations (
id text PRIMARY KEY NOT NULL,
checksum text NOT NULL,
execution_time_in_millis bigint NOT NULL,
applied_at timestamp with time zone NOT NULL
);
CREATE TABLE public.user_answers (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
user_id bigint NOT NULL,
draggable_words_id bigint NOT NULL,
answer text NOT NULL,
correct_answer text NOT NULL,
is_correct boolean NOT NULL GENERATED ALWAYS AS ((answer = correct_answer)) STORED
);
ALTER TABLE public.user_answers
ADD CONSTRAINT user_answers_draggable_words_id_correct_answer_fkey
FOREIGN KEY (draggable_words_id, correct_answer) REFERENCES draggable_words(id, correct_answer) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE public.user_answers
ADD CONSTRAINT user_answers_draggable_words_id_fkey
FOREIGN KEY (draggable_words_id) REFERENCES draggable_words(id) ON DELETE CASCADE;
ALTER TABLE public.user_answers
ADD CONSTRAINT user_answers_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
CREATE TABLE public.user_question_answers (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
answer integer NOT NULL,
user_id bigint NOT NULL,
part_question_id bigint NOT NULL
);
ALTER TABLE public.user_question_answers
ADD CONSTRAINT user_question_answers_user_id_part_question_id_key
UNIQUE (user_id, part_question_id);
ALTER TABLE public.user_question_answers
ADD CONSTRAINT user_question_answers_part_question_id_fkey
FOREIGN KEY (part_question_id) REFERENCES part_questions(id) ON DELETE RESTRICT;
ALTER TABLE public.user_question_answers
ADD CONSTRAINT user_question_answers_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
CREATE TABLE public.words_to_exercises (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
word_id bigint,
exercise_id bigint
);
ALTER TABLE public.words_to_exercises
ADD CONSTRAINT words_to_exercises_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id);
ALTER TABLE public.words_to_exercises
ADD CONSTRAINT words_to_exercises_word_id_fkey
FOREIGN KEY (word_id) REFERENCES draggable_words(id);
+1 -4
View File
@@ -56,10 +56,6 @@ function create() {
}
return val ?? "";
}
// Unused-vars
// function readStringOptional(key, defaultValue) {
// return getFromEnv(key) ?? defaultValue;
// }
function readInt(key) {
const val = getFromEnv(key);
@@ -86,6 +82,7 @@ function create() {
user: readString("DB_USER"),
password: readString("DB_PASSWORD"),
database: readString("DB_NAME"),
ssl: isProduction,
},
};
+1 -1
View File
@@ -79,7 +79,7 @@ export class Migrator {
get migrationsDir() {
return path
.join(__dirname, "../../../../data", "migrations")
.join(__dirname, "../../../data", "migrations")
.replace("dist/", "");
}
}
+1 -5
View File
@@ -4,12 +4,8 @@ import { useState, useEffect, useRef, useMemo } from "react";
import { useIsMounted } from "./useIsMounted";
import { useUser } from "@/context/user.context";
const baseUrl =
process.env.NEXT_ENV === "production"
? "https://tbd.com"
: "http://localhost:3000";
export const axiosInstance = axios.create({
baseURL: `${baseUrl}/api`,
baseURL: `/api`,
});
/**
+3 -3
View File
@@ -4,8 +4,8 @@
"packageManager": "npm@10.2.4",
"description": "GRADESA is a freely accessible website where language learners at language centres as well as university students of German studies can autonomously work on their German grammar skills and knowledge.",
"scripts": {
"db:migrate": "./data/migrate.sh",
"db:reset": "./data/reset-db.sh",
"db:migrate": "./gradesa/data/migrate.sh",
"db:reset": "./gradesa/data/reset-db.sh",
"prepare": "husky install",
"dev": "npm run --prefix gradesa dev",
"format:check": "prettier --check \"**/*.{js,jsx,md,yml}\"",
@@ -29,4 +29,4 @@
},
"author": "",
"license": "ISC"
}
}