diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9673a92..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - develop - pull_request: - branches: - - main - - develop - -jobs: - audit: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x] - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm install - - - name: Audit - run: npm run audit - - lint: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x] - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm install - - - name: Lint - run: npm run lint - diff --git a/.github/workflows/niployments.yaml b/.github/workflows/niployments.yaml new file mode 100644 index 0000000..274b4b1 --- /dev/null +++ b/.github/workflows/niployments.yaml @@ -0,0 +1,22 @@ +name: Deploy + +on: + push: + branches: + - main + - develop + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Upload to NIployments registry + uses: NIAEFEUP/push-to-niployments@v2.2 + with: + docker_dockerfile: Dockerfile + docker_context: ./django + docker_target: prod + NIPLOYMENTS_REGISTRY_URL: ${{ vars.NIPLOYMENTS_REGISTRY_URL }} + NIPLOYMENTS_REGISTRY_USERNAME: ${{ vars.NIPLOYMENTS_REGISTRY_USERNAME }} + NIPLOYMENTS_REGISTRY_PASSWORD: ${{ secrets.NIPLOYMENTS_REGISTRY_PASSWORD }} diff --git a/.gitignore b/.gitignore index eda1cdd..0e087d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,20 +3,19 @@ logs *.log # sigarra internal data cannot be committed -mysql/sql/01_dump_mysql.sql +postgres/sql/01_dump_postgres.sql # dotenv environment variables file .env -# mysql data -mysql/data/* -mysql/sql/01_data.sql +# postgres data +postgres/data/* +postgres/sql/01_data.sql **__pycache__ # django django/**/migrations/** django/university/models.py -django/statistics.sql # celery django/celerybeat-schedule diff --git a/Makefile b/Makefile index 6735e7b..af47740 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: all clean -MYSQL_DATA = ./mysql/sql +POSTGRES_DATA = ./postgres/sql all: clean_database @echo [EXECUTING] ./scripts/$(EXEC) @@ -11,10 +11,10 @@ download: clean_fetcher clean_database @-mkdir ./fetcher/data @echo [DOWNLOADING] data from the source... @docker-compose run fetcher python ./update_data/download.py - @echo [REMOVING] data from mysql... - @-rm $(MYSQL_DATA)/01_data.sql + @echo [REMOVING] data from postgres... + @-rm $(POSTGRES_DATA)/01_data.sql @echo [MOVING] data from fetcher to sql... - @mv ./fetcher/data/* ./mysql/sql + @mv ./fetcher/data/* ./postgres/sql upload: @echo [UPLOADING] data... @@ -27,5 +27,5 @@ clean_fetcher: @-rm -r ./fetcher/data clean_database: - @echo [CLEANING] Removing folder mysql/data... - @-rm -r ./mysql/data/ + @echo [CLEANING] Removing database data... + @-docker volume rm tts_postgres_data || true diff --git a/README.md b/README.md index bfc6079..1cad33a 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,82 @@ -# TTS - backend -The backend for timetable selector. +# TTS - Backend + +The backend for the timetable selector, which is a platform that aims to help students better choose their class schedules by allowing them to see and play with all possible combinations. + +Made with ❤️ by NIAEFEUP. + ## Installation ### Prerequisites - `docker` -- `docker-compose` +- `docker compose` ### Installing docker to install docker, take a look on the [official website](https://www.docker.com/) and follow the [`Get docker`](https://docs.docker.com/get-docker/) section to install it. If you're using windows, make sure to have the [`wsl`](https://docs.microsoft.com/en-us/windows/wsl/install) installed. -In case you're using linux, after installing docker check the [`Manage Docker as a non-root user`](https://docs.docker.com/engine/install/linux-postinstall/), so you can use docker without the `sudo` command. +In case you're using linux, after installing docker check the [`Manage Docker as a non-root user`](https://docs.docker.com/engine/install/linux-postinstall/), so you can use docker without the `sudo` command, which involves creating a user group for docker. ## Data -The data is available the NIAEFEUP drive (Only for NIAEFEUP members): +The data is available at the NIAEFEUP drive (Only for NIAEFEUP members): https://drive.google.com/drive/folders/1hyiwPwwPWhbAPeJm03c0MAo1HTF6s_zK?usp=sharing -- The ```00_schema_mysql.sql``` corresponds to the schema for the most recent data. - -- Copy the ```01_data.sql``` and ```00_schema_mysql.sql``` of year and semester you desire to the ```mysql/sql``` folder. - +- The ```00_schema_postgres.sql``` corresponds to the schema for the most recent data. +- Copy the ```01_data.sql``` and ```00_schema_postgres.sql``` of year and semester you desire to the ```postgres/sql``` folder. ## Usage + ### Development environment -You can start developing by building the local server with docker: + +#### Building the container + +After you installed docker, go to the folder where you cloned this repository and do: ```yaml -docker-compose build . +docker compose build +``` + +This will build the docker container for the backend. + +In case you have __already build the server before and want to repopulate the database__, make sure you run + +```bash +sudo make clean +``` + +We need to clean the database to repopulate it, since the way the postgres container works is that it only runs the `sql` files present in the `postgres/sql` folder if the database is clean. This is way we need to issue `sudo make clean` in order for the insert sql queries to be run. + +#### Running the container + +Before running docker, you have to create an `.env` file with required environment variables for the backend to work. + +```bash +cp .env.dev .env ``` -In case you have __already build the server before and want to build it again__, be sure to delete the folder in `mysql/data`. You can do this by running `sudo rm -r mysql/data/`. To make your life easier, you can simply run the `build_dev.sh` script: `sudo ./build_dev.sh`. -> The sudo permission is nevessary to delete the `mysql/data` folder. +And then you need to set the correct desired values in the `.env` file. + +*The `.env` file is not already on the repository in order to prevent sensitive information to be leaked. This way, the file with default non important values (`.env.dev`) serves as a template, while the real file with important sensitive values is on `.gitignore` so it is never accidentally +uploaded to `github` with sensitive information.* ```yaml -docker-compose up +docker compose up ``` +#### Some django caveats after running the container + +- The first time you run this docker container or after you clean the database, you will need to a wait for some time (5-10 minutes) until the database is populated. It is normal to see django giving a `115` error since the database is not yet ready to anwser to connection requests since it is busy populating itself. + +- There are some times on the first execution of this command that django will start giving a`2` error. If that happens, you need to close the container with `docker compose down` and then turning it on with `docker compose up` again. -As well as the build, the running command can also be executed with the `run_dev.sh` script by executing: `./run_dev.sh`. - +#### Accessing the development database -> __WARNING__: it's likely that the first execution of `docker-compose up` after building won't work, since django doesn't wait for the database being populated to executed. Thus, if that's your ccase, execute it again. +We are currently using `pgadmin` and you can access it +1. Go to `localhost:4000` +2. On the login screen, both the credentials are as follows: + + - Email: admin@example.com + - Password: admin + + This is fine, since this is only a development environment. diff --git a/django/.env.dev b/django/.env.dev index f055815..753ff70 100644 --- a/django/.env.dev +++ b/django/.env.dev @@ -1,8 +1,13 @@ DEBUG=0 SECRET_KEY=foo -MYSQL_DATABASE=tts -MYSQL_PASSWORD=root -MYSQL_USER=root -MYSQL_HOST=db -MYSQL_PORT=3306 +POSTGRES_DB=tts +POSTGRES_USER=root +POSTGRES_PASSWORD=root +POSTGRES_HOST=db +POSTGRES_PORT=5432 + +TTS_REDIS_HOST=tts_redis +TTS_REDIS_PORT=6379 +TTS_REDIS_USERNAME= +TTS_REDIS_PASSWORD= diff --git a/django/Dockerfile b/django/Dockerfile index d5dade9..7f9c001 100644 --- a/django/Dockerfile +++ b/django/Dockerfile @@ -1,21 +1,24 @@ -FROM python:3.8-slim-buster +# deps +FROM python:3.8-slim-buster AS deps WORKDIR /usr/src/django/ # Get's the output from the django in realtime. -ENV PYTHONUNBUFFERED 1 -ENV STATISTICS_NAME tts_be -ENV STATISTICS_PASS batata_frita_123 +ENV PYTHONUNBUFFERED=1 # Copy requirements COPY ./requirements.txt ./requirements.txt -# Dependencies for mysqlclient +# Dependencies for building the requirements RUN apt-get update -RUN apt-get -y install build-essential default-libmysqlclient-dev +RUN apt-get -y install build-essential -# Install mysql command to wait for the database initialization -RUN apt -y install default-mysql-client +# Install postgres dependencies (pgsql client and development files) +COPY ./etc/pgdg.sh /tmp/pgdg.sh +RUN /tmp/pgdg.sh + +RUN apt -y install libpq-dev postgresql-client-16 +RUN apt -y clean && rm -rf /var/lib/apt/lists/* # Install the requirements RUN pip install -r requirements.txt @@ -23,4 +26,13 @@ RUN pip install -r requirements.txt EXPOSE 8000 COPY ./entrypoint.sh ./entrypoint.sh -ENTRYPOINT ["sh", "/usr/src/django/entrypoint.sh"] +ENTRYPOINT ["/usr/src/django/entrypoint.sh"] + +# prod +FROM deps AS prod + +COPY tts_be/ ./tts_be +COPY university/ ./university +COPY manage.py tasks.py ./ + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/django/entrypoint.sh b/django/entrypoint.sh old mode 100644 new mode 100755 index fb723a6..b7e8af4 --- a/django/entrypoint.sh +++ b/django/entrypoint.sh @@ -1,24 +1,22 @@ -#!bin/sh +#!/bin/sh # WARNING: The script will not work if formated with CRLF. # Configure the shell behaviour. set -e -if [[ ${DEBUG} == 1 ]] +if [[ "${DEBUG}" == 1 ]] then set -x fi # Get parameters. -database_host="$1" # The database host and should be provided the container name. -shift cmd="$@" -# Waits for mysql initialization. -until mysql -h "$database_host" -u ${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} -e 'select 1'; do - >&2 echo "MySQL is unavailable - sleeping" +# Waits for PostgreSQL initialization. +until PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" "${POSTGRES_DB}" -c 'select 1'; do + >&2 echo "PostgreSQL is unavailable - sleeping" sleep 4 done ->&2 echo "Mysql is up - executing command" +>&2 echo "PostgreSQL is up - executing command" # Migrate the Django. python manage.py inspectdb > university/models.py diff --git a/django/etc/pgdg.sh b/django/etc/pgdg.sh new file mode 100755 index 0000000..7879e2b --- /dev/null +++ b/django/etc/pgdg.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Source: https://www.postgresql.org/download/linux/ubuntu/ + +# Import the repository signing key: +apt install -y curl ca-certificates postgresql-common lsb-release + +install -d /usr/share/postgresql-common/pgdg +curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc + +# Create the repository configuration file: +sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +# Update the package lists: +apt update diff --git a/django/requirements.txt b/django/requirements.txt index 3eab85a..e4272af 100644 --- a/django/requirements.txt +++ b/django/requirements.txt @@ -5,6 +5,7 @@ django-cors-headers==3.10.1 djangorestframework==3.11.0 pytz==2021.3 sqlparse==0.4.2 -mysqlclient==1.4.6 +psycopg2==2.9.9 celery==5.2.7 redis==3.5.3 +python-dotenv==1.0.1 diff --git a/django/tasks.py b/django/tasks.py index 0520c18..adc35da 100644 --- a/django/tasks.py +++ b/django/tasks.py @@ -1,23 +1,23 @@ from celery import Celery from celery.schedules import crontab import os +from dotenv import dotenv_values -app = Celery('tasks', broker="redis://tts-be-redis_service-1:6379") +CONFIG={ + **dotenv_values(".env"), # load variables + **os.environ, # override loaded values with environment variables +} + +username_password_str = '' +if os.getenv('TTS_REDIS_USERNAME') != '' and os.getenv('TTS_REDIS_PASSWORD') != '': + username_password_str = f"{os.getenv('TTS_REDIS_USERNAME')}:{os.getenv('TTS_REDIS_PASSWORD')}@" + +app = Celery('tasks', broker=f"redis://{username_password_str}{os.getenv('TTS_REDIS_HOST')}:{os.getenv('TTS_REDIS_PORT')}") # Gets called after celery sets up. Creates a worker that runs the dump_statistics function at midnight and noon everyday -@app.on_after_configure.connect -def setup_periodic_tasks(sender, **kwargs): - sender.add_periodic_task( - crontab(minute='0', hour='0, 12'), - dump_statistics.s(), - name='dump statistics' - ) +#@app.on_after_configure.connect +#def setup_periodic_tasks(sender, **kwargs): +# sender.add_periodic_task() + + -@app.task -def dump_statistics(): - command = "mysqldump -P {} -h db -u {} -p{} {} statistics > statistics.sql".format( - os.environ["MYSQL_PORT"], - os.environ["MYSQL_USER"], - os.environ["MYSQL_PASSWORD"], - os.environ["MYSQL_DATABASE"]) - os.system(command) diff --git a/django/tts_be/settings.py b/django/tts_be/settings.py index b5b9d39..a276924 100644 --- a/django/tts_be/settings.py +++ b/django/tts_be/settings.py @@ -13,6 +13,8 @@ from pathlib import Path import os + + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -80,12 +82,12 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': os.getenv('MYSQL_DATABASE'), - 'USER': os.getenv('MYSQL_USER'), - 'PASSWORD': os.getenv('MYSQL_PASSWORD'), - 'HOST': os.getenv('MYSQL_HOST'), - 'PORT': os.getenv('MYSQL_PORT') , + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('POSTGRES_DB'), + 'USER': os.getenv('POSTGRES_USER'), + 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), + 'HOST': os.getenv('POSTGRES_HOST'), + 'PORT': os.getenv('POSTGRES_PORT') , } } diff --git a/django/university/controllers/ClassController.py b/django/university/controllers/ClassController.py new file mode 100644 index 0000000..a6d2a5b --- /dev/null +++ b/django/university/controllers/ClassController.py @@ -0,0 +1,48 @@ +from university.models import Class, Professor, Slot, SlotProfessor + + +class ClassController: + @staticmethod + def get_professors(slot): + slot_professors = SlotProfessor.objects.filter(slot_id=slot.id).values() + + professors = [] + + for slot_professor in slot_professors: + professor = Professor.objects.get(id=slot_professor['professor_id']) + professors.append({ + 'id': professor.id, + 'acronym': professor.professor_acronym, + 'name': professor.professor_name + }) + + return { + 'id': slot.id, + 'lesson_type': slot.lesson_type, + 'day': slot.day, + 'start_time': float(slot.start_time), + 'duration': float(slot.duration), + 'location': slot.location, + 'is_composed': slot.is_composed, + 'professors': professors + } + + @staticmethod + def get_classes(course_unit_id: int): + classes = Class.objects.filter(course_unit=course_unit_id).select_related( + 'course_unit').prefetch_related('slotclass_set__slot').order_by("name") + + result = [] + for class_obj in classes: + slot_ids = [sc.slot_id for sc in class_obj.slotclass_set.all()] + slots = Slot.objects.filter(id__in=slot_ids) + + slot_list = list(map(ClassController.get_professors, slots)) + + result.append({ + 'id': class_obj.id, + 'name': class_obj.name, + 'slots': slot_list + }) + + return result diff --git a/django/university/response/errors.py b/django/university/response/errors.py new file mode 100644 index 0000000..766983b --- /dev/null +++ b/django/university/response/errors.py @@ -0,0 +1,5 @@ +def build_error_dict(msg: str) -> dict: + return {"error: ": msg} + +def course_unit_not_found_error(id: int) -> dict: + return build_error_dict(f"A course unit with id {id} was not found") diff --git a/django/university/stats_example/stats_ttsbe_31-01-23.json b/django/university/stats_example/stats_ttsbe_31-01-23.json deleted file mode 100644 index 8869153..0000000 --- a/django/university/stats_example/stats_ttsbe_31-01-23.json +++ /dev/null @@ -1 +0,0 @@ -"{\"Licenciatura em Biologia\": 1, \"Licenciatura em BioquÃ\u00admica\": 2, \"Licenciatura em Inteligência Artificial e Ciência de Dados\": 14, \"Licenciatura em Matemática Aplicada\": 1, \"Licenciatura em Artes Plásticas\": 1, \"Licenciatura em Design de Comunicação\": 2, \"Licenciatura em Engenharia Agronómica\": 0, \"Licenciatura em Desenho\": 0, \"Licenciatura em Gestão\": 1, \"Direito\": 1, \"Criminologia\": 0, \"Licenciatura em Ciências da Nutrição\": 0, \"Licenciatura em Economia\": 15, \"Licenciatura em LÃ\u00adnguas Aplicadas\": 0, \"Licenciatura em Ciências e Tecnologia do Ambiente\": 0, \"Licenciatura em QuÃ\u00admica\": 1, \"Licenciatura em Ciência da Informação\": 0, \"Ciências do Desporto\": 0, \"Licenciatura em Matemática\": 0, \"Licenciatura em Engenharia FÃ\u00adsica\": 3, \"Licenciatura em Engenharia e Gestão Industrial\": 6, \"Licenciatura em Engenharia Eletrotécnica e de Computadores\": 50, \"Licenciatura em Ciência de Computadores\": 1, \"Licenciatura em Ciências do Meio Aquático\": 1, \"Licenciatura em Geografia\": 0, \"Licenciatura em Sociologia\": 0, \"Licenciatura em Engenharia Mecânica\": 23, \"Licenciatura em Engenharia QuÃ\u00admica\": 4, \"Mestrado Integrado em Medicina Veterinária\": 1, \"Licenciatura em História\": 0, \"Licenciatura em LÃ\u00adnguas e Relações Internacionais\": 0, \"Licenciatura em Ciências da Comunicação\": 2, \"Licenciatura em Engenharia Civil\": 128, \"Mestrado em Ensino de Matemática no 3º Ciclo do Ensino Básico e no Secundário\": 0, \"Licenciatura em Engenharia de Minas e Geo-Ambiente\": 0, \"Mestrado Integrado em Medicina\": 0, \"Licenciatura em Bioengenharia\": 1, \"Licenciatura em LÃ\u00adnguas, Literaturas e Culturas\": 1, \"Licenciatura em Engenharia Geoespacial\": 0, \"Licenciatura em Filosofia\": 0, \"Licenciatura em Psicologia\": 1, \"Licenciatura em Engenharia Informática e Computação\": 190, \"Mestrado em FÃ\u00adsica Médica\": 0, \"Licenciatura em Engenharia do Ambiente\": 0, \"Mestrado Integrado em Medicina Dentária\": 0, \"Mestrado em Biologia Celular e Molecular\": 0, \"Mestrado em Engenharia Matemática\": 0, \"Mestrado em Arquitetura Paisagista\": 0, \"Mestrado em Engenharia FÃ\u00adsica\": 0, \"Mestrado em Economia e Administração de Empresas\": 0, \"Mestrado em QuÃ\u00admica\": 2, \"Mestrado em Economia e Gestão de Recursos Humanos\": 0, \"Licenciatura em Arquitetura Paisagista\": 0, \"Mestrado em BioquÃ\u00admica\": 0, \"Mestrado em Tecnologia e Ciência Alimentar\": 0, \"Mestrado em Geomateriais e Recursos Geológicos\": 0, \"Mestrado em Design Industrial e de Produto\": 0, \"Mestrado em Economia\": 0, \"Licenciatura em Arqueologia\": 0, \"Mestrado em Matemática\": 0, \"Mestrado Integrado em Ciências Farmacêuticas\": 0, \"Licenciatura em Literatura e Estudos Interartes\": 0, \"Licenciatura em Ciências da Linguagem\": 0, \"Mestrado em Engenharia Agronómica\": 1, \"Mestrado em Estudos de Arte\": 0, \"Mestrado em Contabilidade e Controlo de Gestão\": 0, \"Mestrado em Ecologia e Ambiente\": 0, \"Mestrado em Arte e Design para o Espaço Público\": 0, \"Mestrado em Ciências e Tecnologia do Ambiente\": 0, \"Licenciatura em Engenharia de Materiais\": 0, \"Mestrado em Direito\": 0, \"2º Ciclo em Psicologia do Desporto e Desenvolvimento Humano\": 0, \"Licenciatura em Engenharia e Biotecnologia Florestal\": 0, \"Mestrado em Genética Forense\": 0, \"Mestrado em Nutrição Pediátrica\": 0, \"Licenciatura em Geologia\": 0, \"2º Ciclo em Atividade FÃ\u00adsica e Saúde\": 0, \"Mestrado em Economia e Gestão da Inovação\": 0, \"2º Ciclo em Atividade FÃ\u00adsica Adaptada\": 0, \"Licenciatura em História da Arte\": 0, \"Mestrado em Ensino de Biologia e de Geologia no 3ºCiclo do Ensino Básico e no Ensino Secundário\": 0, \"Mestrado em Economia e Gestão do Ambiente\": 0, \"Licenciatura em FÃ\u00adsica\": 0, \"Licenciatura em Ciências da Educação\": 0, \"Mestrado em Design da Imagem\": 0, \"Mestrado em Vinho, Turismo e Inovação - Enoturismo\": 0, \"Mestrado em Artes Plásticas\": 0, \"Mestrado em Educação Alimentar\": 0, \"Mestrado em Ensino e Divulgação das Ciências\": 0, \"Mestrado em Ensino de FÃ\u00adsica e de QuÃ\u00admica no 3ºCiclo do Ensino Básico e no Ensino Secundário\": 0, \"Mestrado em Nutrição Comunitária e Saúde Pública\": 0, \"Programa Doutoral em Contaminação e Toxicologia Ambientais\": 0, \"2º Ciclo em Ensino de Educação FÃ\u00adsica nos Ensinos Básico e Secundário\": 0, \"Programa Doutoral em Ciências de Enfermagem\": 0, \"2º Ciclo em Treino Desportivo - Treino de Alto Rendimento e Treino de Jovens\": 0, \"Mestrado em Avaliação e Remediação de Solos\": 0, \"Mestrado em Economia da Empresa e da Estratégia\": 0, \"Mestrado em Engenharia de Viticultura e Enologia\": 0, \"Mestrado em Engenharia Geográfica\": 0, \"Mestrado em Métodos Avançados e Acreditação em Análise QuÃ\u00admica\": 0, \"Programa Doutoral em Ciências Biomédicas\": 0, \"Programa Doutoral em Ciências Médicas\": 0, \"Programa Doutoral em Ciências do Meio Aquático - Biologia e Ecologia\": 0, \"Mestrado em EstatÃ\u00adstica Computacional e Análise de Dados\": 0, \"2º Ciclo em Atividade FÃ\u00adsica, ExercÃ\u00adcio e Saúde\": 0, \"Mestrado em Ciências do Consumo e Nutrição\": 0, \"Mestrado em Design Gráfico e Projetos Editoriais\": 0, \"Mestrado em Engenharia de Redes e Sistemas Informáticos\": 1, \"Mestrado em Biodiversidade, Genética e Evolução\": 1, \"Mestrado em Criminologia\": 0, \"Mestrado em Deteção Remota\": 0, \"Mestrado em Oncologia\": 0, \"Ciências Gastronómicas\": 0, \"Programa Doutoral em Neurociências\": 0, \"Programa Doutoral em Ciência Animal\": 0, \"Mestrado em Geologia\": 0, \"2º Ciclo em Gestão Desportiva\": 0, \"Programa Doutoral em Biotecnologia Molecular e Celular Aplicada às Ciências da Saúde\": 0, \"2º Ciclo em Atividade FÃ\u00adsica para a Terceira Idade\": 0, \"Programa Doutoral em Patologia e Genética Molecular\": 0, \"Programa Doutoral em Psicologia\": 0, \"Mestrado em Nutrição ClÃ\u00adnica\": 0, \"Programa Doutoral em Gerontologia e Geriatria\": 0, \"Programa Doutoral em Ciências da Educação\": 0, \"Programa Doutoral em Cuidados Paliativos\": 0, \"Programa Doutoral em Biologia Molecular e Celular\": 0, \"Programa Doutoral em Ciências Veterinárias\": 0, \"Programa Doutoral em Biologia Básica e Aplicada\": 0, \"Doutoramento em Medicina Dentária\": 0, \"Mestrado em Alimentação Coletiva\": 0, \"Programa Doutoral em Investigação ClÃ\u00adnica e em Serviços de Saúde\": 0, \"Mestrado em Biologia Funcional e Biotecnologia de Plantas\": 0, \"Programa Doutoral em Biomedicina\": 0, \"Mestrado em Segurança Informática\": 1, \"Mestrado em Ciência de Dados (Data Science)\": 0, \"3º ciclo em Sociologia\": 0, \"Programa Doutoral em Bioética\": 0, \"Programa Doutoral em Ciência de Dados de Saúde\": 0, \"Programa Doutoral em Ciências Forenses\": 0, \"Programa Doutoral em Saúde Pública Global\": 0, \"Mestrado em Biologia e Gestão da Qualidade da Ã\u0081gua\": 0, \"Programa Doutoral em Medicina\": 0, \"3º Ciclo de Estudos em Ciências Sociais e Envelhecimento\": 0, \"Mestrado em Astronomia e AstrofÃ\u00adsica\": 0, \"Programa Doutoral em Sexualidade Humana\": 0, \"Programa Doutoral em Segurança e Saúde Ocupacionais\": 0, \"Programa Doutoral em Saúde Pública\": 0, \"Mestrado em FÃ\u00adsica\": 0, \"3º Ciclo de Estudos em Ciências da Linguagem\": 0, \"Programa Doutoral em Planeamento do Território\": 0, \"Programa Doutoral em Telecomunicações\": 0, \"Programa Doutoral em Saúde Mental\": 0, \"Programa Doutoral em Engenharia Metalúrgica e de Materiais\": 0, \"Programa Doutoral em Ciências Cardiovasculares\": 0, \"Programa Doutoral em Medicina e Oncologia Molecular\": 0, \"Programa Doutoral em Metabolismo - ClÃ\u00adnica e Experimentação\": 0, \"Programa Doutoral em Media Digitais\": 0, \"Programa Doutoral em LÃ\u00adderes para Indústrias Tecnológicas\": 0, \"3º Ciclo de Estudos em Migração e Modernidade: Desafios Históricos e Culturais\": 0, \"Programa Doutoral em Engenharia do Ambiente\": 0, \"3º Ciclo de Estudos em Estudos Literários, Culturais e InterartÃ\u00adsticos\": 0, \"Programa Doutoral em Engenharia e PolÃ\u00adticas Públicas\": 0, \"Programa Doutoral em Engenharia e Gestão Industrial\": 0, \"Programa Doutoral em Engenharia FÃ\u00adsica\": 0, \"Programa Doutoral em Engenharia Mecânica\": 2, \"Mestrado em Ciência de Computadores\": 0, \"Doutoramento em Ciências Farmacêuticas\": 0, \"Programa Doutoral em Sistemas de Transportes\": 0, \"3º ciclo em Geografia\": 0, \"3º ciclo em História\": 0, \"Mestrado em Recursos Biológicos Aquáticos\": 0, \"Programa Doutoral em Farmacologia e Toxicologia Experimentais e ClÃ\u00adnicas\": 0, \"Doutoramento em Gestão\": 0, \"Mestrado em Aplicações em Biotecnologia e Biologia Sintética\": 0, \"Mestrado em Bioinformática e Biologia Computacional\": 0, \"Programa Doutoral em Engenharia Informática\": 0, \"3º Ciclo em Estudos do Património\": 0, \"Programa Doutoral em Engenharia Civil\": 1, \"Programa Doutoral em Filosofia\": 0, \"Doutoramento em Fisioterapia\": 0, \"Programa Doutoral em Ciências do Desporto\": 0, \"Doutoramento em Ciências do Consumo Alimentar e Nutrição\": 0, \"Programa Doutoral em Engenharia de Minas e Geo-Recursos\": 0, \"Programa de Doutoramento em Atividade FÃ\u00adsica e Saúde\": 0, \"Doutoramento em QuÃ\u00admica Sustentável\": 0, \"Doutoramento em Criminologia\": 0, \"Doutoramento em Engenharia Geográfica\": 0, \"Doutoramento em Matemática e Aplicações\": 0, \"Doutoramento em Direito\": 0, \"Mestrado em Ciência e Tecnologia de Nanomateriais\": 0, \"Programa Doutoral em Engenharia Electrotécnica e de Computadores\": 0, \"Doutoramento em Arquitectura Paisagista\": 0, \"Programa Doutoral em Engenharia Biomédica\": 0, \"Doutoramento em Economia\": 0, \"Mestrado em Neurobiologia\": 0, \"Mestrado em Medicina e Oncologia Molecular\": 0, \"Mestrado em Saúde Pública\": 0, \"Doutoramento em Ciências Agrárias\": 0, \"Programa Doutoral em Engenharia QuÃ\u00admica e Biológica\": 0, \"Toxicologia e Contaminação Ambientais\": 0, \"Doutoramento em Informática\": 0, \"Doutoramento em Arquitectura Paisagista e Ecologia Urbana\": 0, \"Doutoramento em Artes Plásticas\": 0, \"Mestrado em Informática Médica\": 0, \"Doutoramento em Educação ArtÃ\u00adstica\": 0, \"Mestrado em Comunicação ClÃ\u00adnica\": 0, \"Doutoramento em Ciência e Tecnologia Alimentar e Nutrição\": 0, \"Mestrado em Cuidados de Saúde Primários\": 0, \"Mestrado em Ciências Forenses\": 0, \"Mestrado em Cirurgia Ortognática e Ortodontia\": 0, \"Programa de Doutoramento em Arquitetura\": 0, \"Mestrado em Ensino de Filosofia no Ensino Secundário\": 0, \"Mestrado em Bioética\": 0, \"Programa Doutoral em Engenharia da Refinação, PetroquÃ\u00admica e QuÃ\u00admica\": 0, \"Doutoramento em Biotecnologia Marinha e Aquacultura\": 0, \"Mestrado em Ensino de Português no 3º Ciclo do Ensino Básico e no Ensino Secundário\": 0, \"Programa Doutoral em Materiais e Processamento Avançados\": 0, \"Mestrado em Reabilitação Oral\": 0, \"Mestrado em Ensino de Geografia no 3º Ciclo do Ensino Básico e no Ensino Secundário\": 0, \"Mestrado em Aconselhamento Genético\": 0, \"Doutoramento em Ciências e Tecnologia do Ambiente\": 0, \"Doutoramento em Astronomia\": 0, \"Mestrado em Ensino de Inglês no 1º Ciclo do Ensino Básico\": 0, \"Mestrado em Ensino de Português e de LÃ\u00adngua Estrangeira no 3º Ciclo do Ensino Básico e no Ensino Secundário nas áreas de especialização de Alemão ou Espanhol ou Francês ou Inglês\": 0, \"Mestrado em Estudos Literários, Culturais e Interartes\": 0, \"Doutoramento em Nutrição ClÃ\u00adnica\": 0, \"Doutoramento em Biodiversidade, Genética e Evolução\": 0, \"Mestrado em Evidência e Decisão em Saúde\": 0, \"Mestrado em Sociologia\": 0, \"Mestrado em Ensino do Inglês e de LÃ\u00adngua Estrangeira no 3º Ciclo do Ensino Básico e no Ensino Secundário, nas áreas de especialização de Alemão ou Espanhol ou Francês\": 0, \"Mestrado em Ciências da Comunicação\": 0, \"Mestrado em Filosofia, PolÃ\u00adtica e Economia\": 0, \"Doutoramento em Design\": 0, \"Mestrado em Tradução e Serviços LinguÃ\u00adsticos\": 0, \"Doutoramento em Geociências\": 0, \"Doutoramento em Ciência de Computadores\": 0, \"Mestrado em Psicologia\": 1, \"Mestrado em Ciências do Mar - Recursos Marinhos\": 0, \"Mestrado em Ciências da Educação\": 0, \"Mestrado em Educação e Formação de Adultos\": 0, \"Doutoramento em QuÃ\u00admica\": 0, \"Mestrado em Cuidados Paliativos\": 0, \"Mestrado em História e Património\": 0, \"Mestrado em Estudos Medievais\": 0, \"Mestrado em Estudos Africanos - Mestrado Europeu Interdisciplinar\": 0, \"Doutoramento em FÃ\u00adsica\": 0, \"Mestrado em Português LÃ\u00adngua Segunda/ LÃ\u00adngua Estrangeira\": 0, \"Mestrado em Metabolismo - Biopatologia e Experimentação\": 0, \"Mestrado em Educação para a Saúde\": 0, \"Mestrado em Riscos, Cidades e Ordenamento do Território\": 0, \"Mestrado em Arqueologia\": 0, \"Mestrado em Medicina Legal\": 0, \"Mestrado em Toxicologia AnalÃ\u00adtica ClÃ\u00adnica e Forense\": 0, \"Mestrado em Engenharia Mecânica\": 12, \"Mestrado em Engenharia de Software\": 0, \"Mestrado em História Contemporânea\": 0, \"Mestrado em Estudos Anglo-Americanos\": 0, \"Mestrado em QuÃ\u00admica Farmacêutica\": 0, \"Mestrado em Ensino de História no 3º ciclo do Ensino Básico e no Ensino Secundário\": 0, \"Mestrado em Temas de Psicologia\": 0, \"Mestrado em Estudos Alemães\": 0, \"Mestrado em Tecnologia Farmacêutica\": 0, \"Mestrado em LinguÃ\u00adstica\": 0, \"Mestrado em Gestão Comercial\": 0, \"Mestrado em Ensino de Artes Visuais no 3º. Ciclo do Ensino Básico e no Ensino Secundário\": 0, \"Mestrado em Gestão de Serviços\": 0, \"Mestrado em Engenharia QuÃ\u00admica\": 0, \"Mestrado em Marketing\": 37, \"Mestrado em Educação Académica e ClÃ\u00adnica\": 0, \"Mestrado em Engenharia Biomédica\": 0, \"Mestrado em Gestão\": 0, \"Mestrado em Engenharia Informática e Computação\": 301, \"Mestrado em Engenharia e Ciência de Dados\": 0, \"Mestrado em Finanças e Fiscalidade\": 0, \"Mestrado em Planeamento e Projecto Urbano\": 0, \"Mestrado em Engenharia e Gestão Industrial\": 2, \"Mestrado em Engenharia de Segurança e Higiene Ocupacionais\": 0, \"Mestrado em História da Arte, Património e Cultura Visual\": 0, \"Mestrado em Engenharia Civil\": 83, \"Mestrado em Controlo da Qualidade\": 0, \"Doutoramento em Ciência, Tecnologia e Gestão do Mar\": 0, \"Mestrado em Análises ClÃ\u00adnicas\": 0, \"Mestrado em Sistemas de Informação Geográfica e Ordenamento de Território\": 0, \"Mestrado em Gestão da Mobilidade Urbana\": 0, \"Mestrado em Museologia\": 0, \"Doutoramento em Ensino e Divulgação das Ciências\": 0, \"Mestrado em Mecânica Computacional\": 0, \"Mestrado em Comunicação e Gestão de Indústrias Criativas\": 0, \"Mestrado em Desafios das Cidades\": 0, \"Mestrado em Filosofia\": 0, \"Mestrado em História, Relações Internacionais e Cooperação\": 0, \"Mestrado em Modelação, Análise de Dados e Sistemas de Apoio à Decisão\": 0, \"Mestrado em Mecânica dos Solos e Engenharia Geotécnica\": 0, \"Doutoramento em Biologia\": 0, \"Mestrado em Finanças\": 0, \"Mestrado em Engenharia Eletrotécnica e de Computadores\": 13, \"Mestrado em Engenharia do Ambiente\": 0, \"Mestrado em Projeto Integrado na Construção de EdifÃ\u00adcios\": 0, \"Mestrado em Estruturas de Engenharia Civil\": 1, \"Mestrado em Visão por Computador\": 0, \"Mestrado em Engenharia de Serviços e Gestão\": 0, \"Mestrado em Engenharia de Materiais\": 0, \"Mestrado em Estudos Africanos\": 0, \"Mestrado em Bioengenharia\": 1, \"Mestrado em Multimédia\": 5, \"Mestrado em Projeto, Construção e Gestão Sustentáveis do Ambiente ConstruÃ\u00addo\": 1, \"Mestrado em Economia e Gestão Internacional\": 0, \"Mestrado em Gestão e Economia de Serviços de Saúde\": 0, \"Mestrado em Inovação e Empreendedorismo Tecnológico\": 0, \"Mestrado em Ciência da Informação\": 0, \"Mestrado em Engenharia de Minas e Geo-Ambiente\": 0}" \ No newline at end of file diff --git a/django/university/statsvisuals.py b/django/university/statsvisuals.py deleted file mode 100644 index 89f29cf..0000000 --- a/django/university/statsvisuals.py +++ /dev/null @@ -1,65 +0,0 @@ -import json -import matplotlib.pyplot as plt -import requests - -FILE_PATH = './stats_example/stats_ttsbe_31-01-23.json' -URL = "https://ni.fe.up.pt/tts/api/statistics/?name=tts_be&password=batata_frita_123" - - -def line_chart(keys, values): - fig = plt.figure(figsize=(10,5)) - plt.plot(keys, values) - plt.title("Analysis Tool Usage - Line Chart") - plt.xlabel("Courses") - plt.ylabel("Frequency") - plt.xticks(rotation=90, fontsize=8) - plt.yticks(fontsize=8) - plt.subplots_adjust(bottom=0.5) - plt.xticks(rotation=50, ha='right') - plt.savefig('./stats_graphs/stats_line_chart.png') - -def bar_chart(keys, values): - fig = plt.figure(figsize=(10,5)) - plt.bar(keys, values) - plt.title("Analysis Tool Usage - Bar Chart") - plt.xlabel("Courses") - plt.ylabel("Frequency") - plt.xticks(rotation=90, fontsize=8) - plt.yticks(fontsize=8) - plt.subplots_adjust(bottom=0.5) - plt.xticks(rotation=55, ha='right') - plt.savefig('./stats_graphs/stats_bar_chart.png') - -def pie_chart(keys, values): - fig = plt.figure(figsize=(10, 5)) - plt.pie(values, labels=keys, autopct='%1.1f%%') - plt.title("Analysis Tool Usage - Pie Chart") - plt.savefig('./stats_graphs/stats_pie_chart.png') - -""" -response = requests.get(URL) - -if response.status_code == 200: - stats = response.json() - stats = eval(stats) -else: - print("Error: Request failed with status code: ", response.status_code) -""" - - -with open(FILE_PATH) as file: - stats = json.load(file) - stats = eval(stats) - -stats = {k: v for k, v in stats.items() if v > 2} - -keys = list(stats.keys()) -values = list(stats.values()) - -# Show Graphs -pie_chart(keys, values) -bar_chart(keys, values) -line_chart(keys, values) -plt.show() - - diff --git a/django/university/tests.py b/django/university/tests.py index 29077a7..9765ba5 100644 --- a/django/university/tests.py +++ b/django/university/tests.py @@ -1,4 +1,4 @@ from django.test import TestCase # Create your tests here. -print("ola") \ No newline at end of file +print("olá") \ No newline at end of file diff --git a/django/university/urls.py b/django/university/urls.py index 64087b7..9e216ca 100644 --- a/django/university/urls.py +++ b/django/university/urls.py @@ -6,11 +6,10 @@ path('faculty/', views.faculty), path('course/', views.course), path('course_units////', views.course_units), - path('course_units_by_year////', views.course_units_by_year), - path('course_last_year//', views.course_last_year), - path('schedule//', views.schedule), - path('statistics/', views.data), - path('professors//', views.professor), - path('info/', views.info) + path('course_unit//', views.course_unit_by_id), + path('class//', views.classes), + path('professors//', views.professor), + path('info/', views.info), + path('course_unit/hash', views.get_course_unit_hashes) ] diff --git a/django/university/views.py b/django/university/views.py index 104e12f..48513bf 100644 --- a/django/university/views.py +++ b/django/university/views.py @@ -1,155 +1,116 @@ -from django.http.response import HttpResponse from university.models import Faculty from university.models import Course from university.models import CourseUnit -from university.models import Schedule from university.models import Professor -from university.models import ScheduleProfessor +from university.models import SlotProfessor from university.models import CourseMetadata -from university.models import Statistics from university.models import Info +from university.controllers.ClassController import ClassController +from university.response.errors import course_unit_not_found_error from django.http import JsonResponse -from django.core import serializers from rest_framework.decorators import api_view -from django.db.models import Max -from django.db import transaction -import json -import os +from rest_framework import status + +import os from django.utils import timezone -# Create your views here. +from django.forms.models import model_to_dict def get_field(value): return value.field - + + @api_view(['GET']) -def faculty(request): +def faculty(request): json_data = list(Faculty.objects.values()) return JsonResponse(json_data, safe=False) + """ - Returns all the major/major. + Returns all the major/major. REQUEST: http://localhost:8000/course/ """ + + @api_view(['GET']) def course(request, year): json_data = list(Course.objects.filter(year=year).values()) return JsonResponse(json_data, safe=False) + +@api_view(['GET']) +def course_unit_by_id(request, course_unit_id): + course_unit = CourseUnit.objects.filter(id=course_unit_id).first() + if (course_unit == None): + return JsonResponse(course_unit_not_found_error(course_unit_id), status=status.HTTP_404_NOT_FOUND) + + return JsonResponse(model_to_dict(course_unit), safe=False) + + """ - Return all the units from a course/major. + Return all the units from a course/major. REQUEST: course_units//// """ + @api_view(['GET']) -def course_units(request, course_id, year, semester): +def course_units(request, course_id, year, semester): # Fetch CourseUnitYear model instances that match the attributes from the api url parameters. - course_units_metadata = CourseMetadata.objects.filter(course__id = course_id, course_unit__semester = semester, course__year = year).select_related('course_unit').order_by('course_unit_year') + course_units_metadata = CourseMetadata.objects.filter( + course__id=course_id, course_unit__semester=semester, course__year=year).select_related('course_unit').order_by('course_unit_year') json_data = list() # For each object in those course unit year objects we append the CourseUnit dictionary - for course_units in course_units_metadata: - course_units.__dict__.update(course_units.course_unit.__dict__) - del course_units.__dict__["_state"] - json_data.append(course_units.__dict__) - - course = Course.objects.get(id = course_id) - - with transaction.atomic(): - statistics, created = Statistics.objects.select_for_update().get_or_create( - course_unit_id = course_id, - acronym = course.acronym, - defaults = {"visited_times": 0, "last_updated": timezone.now()}, - ) - statistics.visited_times += 1 - statistics.last_updated = timezone.now() - statistics.save() + for course_unit_metadata in course_units_metadata: + course_unit_metadata.__dict__.update( + course_unit_metadata.course_unit.__dict__) + + del course_unit_metadata.__dict__["_state"] + + json_data.append(course_unit_metadata.__dict__) return JsonResponse(json_data, safe=False) + """ - Returns the last year of a course. + Returns the classes of a course unit. """ -@api_view(['GET']) -def course_units_by_year(request, course_id, year, semester): - course_units_metadata = CourseMetadata.objects.filter(course__id = course_id, course_unit__semester = semester, course__year = year).select_related('course_unit') - json_data = list() - # For each object in those course unit year objects we append the CourseUnit dictionary - for course_units in course_units_metadata: - course_units.__dict__.update(course_units.course_unit.__dict__) - del course_units.__dict__["_state"] - json_data.append(course_units.__dict__) +@ api_view(['GET']) +def classes(request, course_unit_id): + return JsonResponse(ClassController.get_classes(course_unit_id), safe=False) - return JsonResponse(json_data, safe=False) """ - Returns the last year of a course. + Returns all the professors of a class of the class id """ -@api_view(['GET']) -def course_last_year(request, course_id): - max_year = CourseMetadata.objects.filter(course__id=course_id).aggregate(Max('course_unit_year')).get('course_unit_year__max') - json_data = {"max_year": max_year} - return JsonResponse(json_data, safe=False) -""" - Returns the schedule of a course unit. -""" -@api_view(['GET']) -def schedule(request, course_unit_id): - course_unit = CourseUnit.objects.get(pk=course_unit_id) - faculty = course_unit.url.split('/')[3] - schedules = list(Schedule.objects.filter(course_unit=course_unit_id).order_by('class_name').values()) - for schedule in schedules: - schedule_professors = list(ScheduleProfessor.objects.filter(schedule=schedule['id']).values()) - professors_link = f'https://sigarra.up.pt/{faculty}/pt/{"hor_geral.composto_doc?p_c_doc=" if schedule["is_composed"] else "func_geral.FormView?p_codigo="}{schedule["professor_sigarra_id"]}' - schedule['professors_link'] = professors_link - del schedule['professor_sigarra_id'] - professors_information = [] - for schedule_professor in schedule_professors: - professors_information.append({ - 'acronym': Professor.objects.get(pk=schedule_professor['professor_sigarra_id']).professor_acronym, - 'name': Professor.objects.get(pk=schedule_professor['professor_sigarra_id']).professor_name - }) - schedule['professor_information'] = professors_information - return JsonResponse(schedules, safe=False) -""" - Returns the statistics of the requests. -""" -@api_view(['GET']) -def data(request): - name = request.GET.get('name') - password = request.GET.get('password') - if name == os.environ['STATISTICS_NAME'] and password == os.environ['STATISTICS_PASS']: - json_data = serializers.serialize("json", Statistics.objects.all()) - return HttpResponse(json_data, content_type='application/json') - else: - return HttpResponse(status=401) +@ api_view(["GET"]) +def professor(request, slot): + slot_professors = list(SlotProfessor.objects.filter(slot_id=slot).values()) -""" - Returns all the professors of a class of the schedule id -""" -@api_view(["GET"]) -def professor(request, schedule): - schedule_professors = list(ScheduleProfessor.objects.filter(schedule=schedule).values()) professors = [] - for schedule_professor in schedule_professors: - professor = Professor.objects.get(pk=schedule_professor['professor_sigarra_id']) + + for slot_professor in slot_professors: + professor = Professor.objects.get(id=slot_professor['professor_id']) professors.append({ - 'sigarra_id': professor.sigarra_id, - 'professor_acronym': professor.professor_acronym, - 'professor_name': professor.professor_name + 'id': professor.id, + 'acronym': professor.professor_acronym, + 'name': professor.professor_name }) + return JsonResponse(professors, safe=False) """ Returns the contents of the info table """ -@api_view(["GET"]) + + +@ api_view(["GET"]) def info(request): info = Info.objects.first() if info: @@ -159,3 +120,30 @@ def info(request): return JsonResponse(json_data, safe=False) else: return JsonResponse({}, safe=False) + + +""" + Verifies if course units have the correct hash +""" + + +@api_view(['GET']) +def get_course_unit_hashes(request): + + ids_param = request.query_params.get('ids', '') + + try: + course_unit_ids = [int(id) for id in ids_param.split(',') if id] + except ValueError: + return JsonResponse({'error': 'Invalid ID format'}, status=400) + + results = {} + + for course_unit_id in course_unit_ids: + try: + course_unit = CourseUnit.objects.get(id=course_unit_id) + results[course_unit_id] = course_unit.hash + except CourseUnit.DoesNotExist: + results[course_unit_id] = None + + return JsonResponse(results, safe=False) diff --git a/docker-compose.yaml b/docker-compose.yaml index dc8740a..6fa1dc4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,38 +1,42 @@ -version: "3.7" +version: "3.7" services: db: - build: ./mysql + build: ./postgres container_name: tts_db - volumes: - - ./mysql/data:/var/lib/mysql + volumes: + - postgres_data:/var/lib/postgresql/data/ env_file: - - mysql/.env.dev + - postgres/.env.dev ports: - - 3306:3306 + - 5432:5432 ulimits: nofile: soft: 262144 hard: 262144 - phpmyadmin: - image: phpmyadmin:5.1.1-apache - container_name: tts_phpmyadmin + pgadmin: + build: + context: ./pgadmin + container_name: tts_pgadmin restart: always + env_file: + - pgadmin/.env.dev + volumes: + - pgadmin_data:/var/lib/pgadmin ports: - 4000:80 django: - build: ./django + build: ./django container_name: tts_django env_file: - django/.env.dev - command: ["db", "python", "manage.py", "runserver", "0.0.0.0:8000"] + command: ["python", "manage.py", "runserver", "0.0.0.0:8000"] volumes: - ./django/:/usr/src/django/ ports: - 8100:8000 - fetcher: build: ./fetcher @@ -42,6 +46,12 @@ services: redis_service: image: redis:6.2-bullseye + container_name: tts_redis restart: always - ports: - - '6379:6379' + ports: + - "6379:6379" + +volumes: + postgres_data: + name: tts_postgres_data + pgadmin_data: diff --git a/docs/schema.drawio b/docs/schema.drawio deleted file mode 100644 index 7831057..0000000 --- a/docs/schema.drawio +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/schema.png b/docs/schema.png index 7685c52..4ebf350 100644 Binary files a/docs/schema.png and b/docs/schema.png differ diff --git a/mysql/.env.dev b/mysql/.env.dev deleted file mode 100644 index 71e60b7..0000000 --- a/mysql/.env.dev +++ /dev/null @@ -1,10 +0,0 @@ -DEBUG=1 -SECRET_KEY=foo - -MYSQL_DATABASE=tts -MYSQL_PASSWORD=root -MYSQL_ROOT_PASSWORD=root -MYSQL_HOST=db -MYSQL_PORT=3306 -STATISTICS_NAME=tts_be -STATISTICS_PASS=batata_frita_123 diff --git a/mysql/Dockerfile b/mysql/Dockerfile deleted file mode 100644 index 0a0fbf3..0000000 --- a/mysql/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM --platform=linux/amd64 mysql:5 - -# Source: https://hub.docker.com/_/mysql -# When a container is started for the first time, a new database with the -# specified name will be created and initialized with the provided configuration variables. -# Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. -# Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into -#that directory and provide custom images with contributed data. SQL files will be imported by default to the -# database specified by the MYSQL_DATABASE variable. -ENV LANG=C.UTF-8 -ADD ./sql/*.sql docker-entrypoint-initdb.d/ \ No newline at end of file diff --git a/mysql/sql/00_schema_mysql.sql b/mysql/sql/00_schema_mysql.sql deleted file mode 100644 index 3c691d5..0000000 --- a/mysql/sql/00_schema_mysql.sql +++ /dev/null @@ -1,196 +0,0 @@ --- phpMyAdmin SQL Dump --- version 4.7.7 - - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET AUTOCOMMIT = 0; -START TRANSACTION; -SET time_zone = "+00:00"; - --- --- Database: `tts` --- - - -CREATE TABLE `faculty` ( - `acronym` varchar(10) DEFAULT NULL, - `name` text, - `last_updated` datetime NOT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - - --- -------------------------------------------------------- - --- --- Table structure for table `course` --- - -CREATE TABLE `course` ( - `id` int(11) NOT NULL, - `faculty_id` varchar(10) NOT NULL, - `sigarra_id` int(11) NOT NULL, - `name` varchar(200) NOT NULL, - `acronym` varchar(10) NOT NULL, - `course_type` varchar(2) NOT NULL, - `year` int(11) NOT NULL, - `url` varchar(2000) NOT NULL, - `plan_url` varchar(2000) NOT NULL, - `last_updated` datetime NOT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - - --- -------------------------------------------------------- - --- --- Table structure for table `course_unit` --- - -CREATE TABLE `course_unit` ( - `id` int(11) NOT NULL, - `sigarra_id` int(11) NOT NULL, - `course_id` int(11) NOT NULL, - `name` varchar(200) NOT NULL, - `acronym` varchar(16) NOT NULL, - `url` varchar(2000) NOT NULL, - `semester` int(4) NOT NULL, - `year` smallint(6) NOT NULL, - `schedule_url` varchar(2000) DEFAULT NULL, - `last_updated` datetime NOT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `course_metadata` --- - -CREATE TABLE `course_metadata` ( - `course_id` int(11) NOT NULL, - `course_unit_id` int(11) NOT NULL, - `course_unit_year` int(4) NOT NULL, - `ects` float(4) NOT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- --- --- Table structure for table `schedule` --- - -CREATE TABLE `schedule` ( - `id` INTEGER NOT NULL, - `day` int(3) NOT NULL, - `duration` decimal(3,1) NOT NULL, - `start_time` decimal(3,1) NOT NULL, - `location` varchar(31) NOT NULL, - `lesson_type` varchar(3) NOT NULL, - `is_composed` boolean NOT NULL, - `professor_sigarra_id` INTEGER, - `course_unit_id` int(11) NOT NULL, - `last_updated` datetime NOT NULL, - `class_name` varchar(31) NOT NULL, - `composed_class_name` varchar(16) DEFAULT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `schedule_professor` --- - -CREATE TABLE `schedule_professor` ( - `schedule_id` INTEGER NOT NULL, - `professor_sigarra_id` INTEGER NOT NULL -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `professor` --- - -CREATE TABLE `professor` ( - `sigarra_id` INTEGER, - `professor_acronym` varchar(16), - `professor_name` varchar(100) -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- - --- --- Table structure for table `info` --- - -CREATE TABLE `info` ( - `date` datetime -) ENGINE=InnoDB CHARSET = utf8 COLLATE = utf8_general_ci; - --- -------------------------------------------------------- - --- Table structure for table `statistics` --- - -CREATE TABLE `statistics` ( - `course_unit_id` int(11) NOT NULL, - `acronym` varchar(10) NOT NULL, - `visited_times` int(11) NOT NULL, - `last_updated` datetime NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - - --- Add primary keys -alter TABLE faculty ADD PRIMARY KEY (`acronym`); - -alter TABLE course ADD PRIMARY KEY (`id`); -alter TABLE course ADD FOREIGN KEY (`faculty_id`) REFERENCES `faculty`(`acronym`) on DELETE CASCADE ON UPDATE CASCADE; - -alter TABLE course_unit ADD PRIMARY KEY (`id`); -alter TABLE course_unit ADD FOREIGN KEY (`course_id`) REFERENCES `course`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - -alter TABLE course_metadata ADD PRIMARY KEY (`course_id`, `course_unit_id`, `course_unit_year`); -alter TABLE course_metadata ADD FOREIGN KEY (`course_unit_id`) REFERENCES `course_unit`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -alter TABLE course_metadata ADD FOREIGN KEY (`course_id`) REFERENCES `course`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - -alter TABLE schedule ADD PRIMARY KEY (`id`); -alter TABLE schedule ADD FOREIGN KEY (`course_unit_id`) REFERENCES `course_unit`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - -alter TABLE info ADD PRIMARY KEY (`date`); - -alter TABLE statistics ADD PRIMARY KEY (`course_unit_id`); - -alter TABLE professor ADD PRIMARY KEY (`sigarra_id`); - -alter TABLE schedule_professor ADD PRIMARY KEY (`schedule_id`, `professor_sigarra_id`); -alter TABLE schedule_professor ADD FOREIGN KEY (`schedule_id`) REFERENCES `schedule`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - - --- --- Indexes for table `course` --- -CREATE UNIQUE INDEX `course_course_id` ON `course` (`sigarra_id`,`faculty_id`,`year`); -CREATE INDEX `course_faculty_acronym` ON `course` (`faculty_id`); - --- --- Indexes for table `course_unit` --- -CREATE UNIQUE INDEX `course_unit_uniqueness` ON `course_unit` (`sigarra_id`,`course_id`,`year`,`semester`); -CREATE INDEX `course_unit_course_id` ON `course_unit` (`course_id`); - --- --- Indexes for table `faculty` --- -CREATE UNIQUE INDEX `faculty_acronym` ON `faculty`(`acronym`); - --- --- Indexes for table `schedule` --- -CREATE INDEX `schedule_course_unit_id` ON `schedule`(`course_unit_id`); - --- --- Indexes for table `schedule` --- -CREATE INDEX `statistics` ON `statistics`(`course_unit_id`); - --- --- Indexes for table `course_metadata` --- -CREATE INDEX `course_metadata_index` ON `course_metadata`(`course_id`, `course_unit_id`, `course_unit_year`); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..44f2058 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "tts-be", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/pgadmin/.env.dev b/pgadmin/.env.dev new file mode 100644 index 0000000..c59f858 --- /dev/null +++ b/pgadmin/.env.dev @@ -0,0 +1,2 @@ +PGADMIN_DEFAULT_EMAIL=admin@example.com +PGADMIN_DEFAULT_PASSWORD=admin diff --git a/pgadmin/Dockerfile b/pgadmin/Dockerfile new file mode 100644 index 0000000..55bc8e8 --- /dev/null +++ b/pgadmin/Dockerfile @@ -0,0 +1,3 @@ +FROM dpage/pgadmin4:8 + +COPY ./servers.json /pgadmin4/servers.json diff --git a/pgadmin/servers.json b/pgadmin/servers.json new file mode 100644 index 0000000..e9c8b08 --- /dev/null +++ b/pgadmin/servers.json @@ -0,0 +1,13 @@ +{ + "Servers": { + "1": { + "Name": "Development Server", + "Group": "Servers", + "Host": "db", + "Port": 5432, + "Username": "root", + "MaintenanceDB": "postgres", + "SSLMode": "prefer" + } + } +} \ No newline at end of file diff --git a/postgres/.dockerignore b/postgres/.dockerignore new file mode 100644 index 0000000..1ecc1e6 --- /dev/null +++ b/postgres/.dockerignore @@ -0,0 +1,2 @@ +/data/ +/.env.dev diff --git a/postgres/.env.dev b/postgres/.env.dev new file mode 100644 index 0000000..613fc5c --- /dev/null +++ b/postgres/.env.dev @@ -0,0 +1,3 @@ +POSTGRES_DB=tts +POSTGRES_USER=root +POSTGRES_PASSWORD=root diff --git a/postgres/Dockerfile b/postgres/Dockerfile new file mode 100644 index 0000000..0c83f74 --- /dev/null +++ b/postgres/Dockerfile @@ -0,0 +1,4 @@ +FROM postgres:16-alpine + +ENV LANG=C.UTF-8 +ADD ./sql/*.sql docker-entrypoint-initdb.d/ diff --git a/postgres/sql/00_schema_postgres.sql b/postgres/sql/00_schema_postgres.sql new file mode 100644 index 0000000..8be090a --- /dev/null +++ b/postgres/sql/00_schema_postgres.sql @@ -0,0 +1,481 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 16.4 +-- Dumped by pg_dump version 16.3 + +-- Started on 2024-08-13 22:21:25 UTC + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- TOC entry 224 (class 1259 OID 16798) +-- Name: class; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."class" ( + "id" bigint NOT NULL, + "name" character varying(31) NOT NULL, + "course_unit_id" integer NOT NULL, + "last_updated" timestamp with time zone NOT NULL +); + + +-- +-- TOC entry 223 (class 1259 OID 16797) +-- Name: class_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."class_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- TOC entry 3474 (class 0 OID 0) +-- Dependencies: 223 +-- Name: class_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."class_id_seq" OWNED BY "public"."class"."id"; + + +-- +-- TOC entry 217 (class 1259 OID 16756) +-- Name: course; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."course" ( + "id" integer NOT NULL, + "faculty_id" character varying(10) NOT NULL, + "name" character varying(200) NOT NULL, + "acronym" character varying(10) NOT NULL, + "course_type" character varying(2) NOT NULL, + "year" integer NOT NULL, + "url" character varying(2000) NOT NULL, + "plan_url" character varying(2000) NOT NULL, + "last_updated" timestamp with time zone NOT NULL +); + + +-- +-- TOC entry 227 (class 1259 OID 16816) +-- Name: course_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."course_metadata" ( + "course_id" integer NOT NULL, + "course_unit_id" integer NOT NULL, + "course_unit_year" integer NOT NULL, + "ects" double precision NOT NULL +); + + +-- +-- TOC entry 222 (class 1259 OID 16785) +-- Name: course_unit; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."course_unit" ( + "id" integer NOT NULL, + "course_id" integer NOT NULL, + "name" character varying(200) NOT NULL, + "acronym" character varying(16) NOT NULL, + "url" character varying(2000) NOT NULL, + "semester" integer NOT NULL, + "year" smallint NOT NULL, + "schedule_url" character varying(2000), + "last_updated" timestamp with time zone NOT NULL, + "hash" character varying(64) +); + + +-- +-- TOC entry 218 (class 1259 OID 16763) +-- Name: faculty; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."faculty" ( + "acronym" character varying(10) NOT NULL, + "name" "text", + "last_updated" timestamp with time zone NOT NULL +); + + +-- +-- TOC entry 219 (class 1259 OID 16770) +-- Name: info; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."info" ( + "date" timestamp with time zone NOT NULL +); + + +-- +-- TOC entry 220 (class 1259 OID 16775) +-- Name: professor; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."professor" ( + "id" integer NOT NULL, + "professor_acronym" character varying(32), + "professor_name" character varying(100) +); + + +-- +-- TOC entry 221 (class 1259 OID 16780) +-- Name: slot; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."slot" ( + "id" integer NOT NULL, + "lesson_type" character varying(3) NOT NULL, + "day" integer NOT NULL, + "start_time" numeric(3,1) NOT NULL, + "duration" numeric(3,1) NOT NULL, + "location" character varying(31) NOT NULL, + "is_composed" integer NOT NULL, + "professor_id" integer, + "last_updated" timestamp with time zone NOT NULL +); + + +-- +-- TOC entry 226 (class 1259 OID 16811) +-- Name: slot_class; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."slot_class" ( + "slot_id" integer NOT NULL, + "class_id" bigint NOT NULL +); + + +-- +-- TOC entry 225 (class 1259 OID 16806) +-- Name: slot_professor; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."slot_professor" ( + "slot_id" integer NOT NULL, + "professor_id" integer NOT NULL +); + + +-- +-- TOC entry 3276 (class 2604 OID 16801) +-- Name: class id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."class" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."class_id_seq"'::"regclass"); + + +-- +-- TOC entry 3299 (class 2606 OID 16833) +-- Name: class class_name_course_unit_id_b5fc7353_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."class" + ADD CONSTRAINT "class_name_course_unit_id_b5fc7353_uniq" UNIQUE ("name", "course_unit_id"); + + +-- +-- TOC entry 3301 (class 2606 OID 16803) +-- Name: class class_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."class" + ADD CONSTRAINT "class_pkey" PRIMARY KEY ("id"); + + +-- +-- TOC entry 3280 (class 2606 OID 16805) +-- Name: course course_id_faculty_id_year_a0a480cc_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course" + ADD CONSTRAINT "course_id_faculty_id_year_a0a480cc_uniq" UNIQUE ("id", "faculty_id", "year"); + + +-- +-- TOC entry 3313 (class 2606 OID 16867) +-- Name: course_metadata course_metadata_course_id_course_unit_id_a53bd8fd_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_metadata" + ADD CONSTRAINT "course_metadata_course_id_course_unit_id_a53bd8fd_uniq" UNIQUE ("course_id", "course_unit_id", "course_unit_year"); + + +-- +-- TOC entry 3316 (class 2606 OID 16820) +-- Name: course_metadata course_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_metadata" + ADD CONSTRAINT "course_metadata_pkey" PRIMARY KEY ("course_id", "course_unit_id", "course_unit_year"); + + +-- +-- TOC entry 3282 (class 2606 OID 16762) +-- Name: course course_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course" + ADD CONSTRAINT "course_pkey" PRIMARY KEY ("id"); + + +-- +-- TOC entry 3294 (class 2606 OID 16823) +-- Name: course_unit course_unit_id_course_id_year_semester_5b83b50d_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_unit" + ADD CONSTRAINT "course_unit_id_course_id_year_semester_5b83b50d_uniq" UNIQUE ("id", "course_id", "year", "semester"); + + +-- +-- TOC entry 3296 (class 2606 OID 16791) +-- Name: course_unit course_unit_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_unit" + ADD CONSTRAINT "course_unit_pkey" PRIMARY KEY ("id"); + + +-- +-- TOC entry 3285 (class 2606 OID 16769) +-- Name: faculty faculty_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."faculty" + ADD CONSTRAINT "faculty_pkey" PRIMARY KEY ("acronym"); + + +-- +-- TOC entry 3287 (class 2606 OID 16774) +-- Name: info info_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."info" + ADD CONSTRAINT "info_pkey" PRIMARY KEY ("date"); + + +-- +-- TOC entry 3289 (class 2606 OID 16779) +-- Name: professor professor_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."professor" + ADD CONSTRAINT "professor_pkey" PRIMARY KEY ("id"); + + +-- +-- TOC entry 3309 (class 2606 OID 16815) +-- Name: slot_class slot_class_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_class" + ADD CONSTRAINT "slot_class_pkey" PRIMARY KEY ("slot_id", "class_id"); + + +-- +-- TOC entry 3311 (class 2606 OID 16854) +-- Name: slot_class slot_class_slot_id_class_id_618e5482_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_class" + ADD CONSTRAINT "slot_class_slot_id_class_id_618e5482_uniq" UNIQUE ("slot_id", "class_id"); + + +-- +-- TOC entry 3291 (class 2606 OID 16784) +-- Name: slot slot_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot" + ADD CONSTRAINT "slot_pkey" PRIMARY KEY ("id"); + + +-- +-- TOC entry 3303 (class 2606 OID 16810) +-- Name: slot_professor slot_professor_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_professor" + ADD CONSTRAINT "slot_professor_pkey" PRIMARY KEY ("slot_id", "professor_id"); + + +-- +-- TOC entry 3306 (class 2606 OID 16841) +-- Name: slot_professor slot_professor_slot_id_professor_id_0a3129c4_uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_professor" + ADD CONSTRAINT "slot_professor_slot_id_professor_id_0a3129c4_uniq" UNIQUE ("slot_id", "professor_id"); + + +-- +-- TOC entry 3297 (class 1259 OID 16839) +-- Name: class_course_unit_id_964f4d1d; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "class_course_unit_id_964f4d1d" ON "public"."class" USING "btree" ("course_unit_id"); + + +-- +-- TOC entry 3277 (class 1259 OID 16830) +-- Name: course_faculty_id_ef24d5b8; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "course_faculty_id_ef24d5b8" ON "public"."course" USING "btree" ("faculty_id"); + + +-- +-- TOC entry 3278 (class 1259 OID 16831) +-- Name: course_faculty_id_ef24d5b8_like; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "course_faculty_id_ef24d5b8_like" ON "public"."course" USING "btree" ("faculty_id" "varchar_pattern_ops"); + + +-- +-- TOC entry 3314 (class 1259 OID 16878) +-- Name: course_metadata_course_unit_id_8acf031c; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "course_metadata_course_unit_id_8acf031c" ON "public"."course_metadata" USING "btree" ("course_unit_id"); + + +-- +-- TOC entry 3292 (class 1259 OID 16829) +-- Name: course_unit_course_id_b0c453a5; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "course_unit_course_id_b0c453a5" ON "public"."course_unit" USING "btree" ("course_id"); + + +-- +-- TOC entry 3283 (class 1259 OID 16821) +-- Name: faculty_acronym_fd9686a8_like; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "faculty_acronym_fd9686a8_like" ON "public"."faculty" USING "btree" ("acronym" "varchar_pattern_ops"); + + +-- +-- TOC entry 3307 (class 1259 OID 16865) +-- Name: slot_class_class_id_050cb01e; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "slot_class_class_id_050cb01e" ON "public"."slot_class" USING "btree" ("class_id"); + + +-- +-- TOC entry 3304 (class 1259 OID 16852) +-- Name: slot_professor_professor_id_7f0a06e1; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "slot_professor_professor_id_7f0a06e1" ON "public"."slot_professor" USING "btree" ("professor_id"); + + +-- +-- TOC entry 3319 (class 2606 OID 16834) +-- Name: class class_course_unit_id_964f4d1d_fk_course_unit_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."class" + ADD CONSTRAINT "class_course_unit_id_964f4d1d_fk_course_unit_id" FOREIGN KEY ("course_unit_id") REFERENCES "public"."course_unit"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3317 (class 2606 OID 16792) +-- Name: course course_faculty_id_ef24d5b8_fk_faculty_acronym; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course" + ADD CONSTRAINT "course_faculty_id_ef24d5b8_fk_faculty_acronym" FOREIGN KEY ("faculty_id") REFERENCES "public"."faculty"("acronym") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3324 (class 2606 OID 16868) +-- Name: course_metadata course_metadata_course_id_d10b5aca_fk_course_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_metadata" + ADD CONSTRAINT "course_metadata_course_id_d10b5aca_fk_course_id" FOREIGN KEY ("course_id") REFERENCES "public"."course"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3325 (class 2606 OID 16873) +-- Name: course_metadata course_metadata_course_unit_id_8acf031c_fk_course_unit_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_metadata" + ADD CONSTRAINT "course_metadata_course_unit_id_8acf031c_fk_course_unit_id" FOREIGN KEY ("course_unit_id") REFERENCES "public"."course_unit"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3318 (class 2606 OID 16824) +-- Name: course_unit course_unit_course_id_b0c453a5_fk_course_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."course_unit" + ADD CONSTRAINT "course_unit_course_id_b0c453a5_fk_course_id" FOREIGN KEY ("course_id") REFERENCES "public"."course"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3322 (class 2606 OID 16860) +-- Name: slot_class slot_class_class_id_050cb01e_fk_class_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_class" + ADD CONSTRAINT "slot_class_class_id_050cb01e_fk_class_id" FOREIGN KEY ("class_id") REFERENCES "public"."class"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3323 (class 2606 OID 16855) +-- Name: slot_class slot_class_slot_id_8ac0e819_fk_slot_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_class" + ADD CONSTRAINT "slot_class_slot_id_8ac0e819_fk_slot_id" FOREIGN KEY ("slot_id") REFERENCES "public"."slot"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3320 (class 2606 OID 16847) +-- Name: slot_professor slot_professor_professor_id_7f0a06e1_fk_professor_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_professor" + ADD CONSTRAINT "slot_professor_professor_id_7f0a06e1_fk_professor_id" FOREIGN KEY ("professor_id") REFERENCES "public"."professor"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- +-- TOC entry 3321 (class 2606 OID 16842) +-- Name: slot_professor slot_professor_slot_id_ddc4b9c2_fk_slot_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."slot_professor" + ADD CONSTRAINT "slot_professor_slot_id_ddc4b9c2_fk_slot_id" FOREIGN KEY ("slot_id") REFERENCES "public"."slot"("id") DEFERRABLE INITIALLY DEFERRED; + + +-- Completed on 2024-08-13 22:21:25 UTC + +-- +-- PostgreSQL database dump complete +--