Skip to content

Commit f738109

Browse files
authored
Merge pull request #450 from open-source-uc/dev
Actualizar main
2 parents d769d47 + 83fcf65 commit f738109

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2174
-2027
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@
4141
"charliermarsh.ruff",
4242
"kokakiwi.vscode-just",
4343
"redhat.ansible",
44-
"github.vscode-github-actions",
45-
"ms-python.black-formatter"
44+
"github.vscode-github-actions"
4645
],
4746
"settings": {
4847
"terminal.integrated.env.linux": {

.github/workflows/lint.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,12 @@ jobs:
3939
poetry install
4040
poetry run prisma generate
4141
42-
- name: Run black
43-
uses: wearerequired/lint-action@v2
42+
- name: Run ruff formatter
43+
uses: chartboost/ruff-action@v1
4444
with:
45-
github_token: ${{ secrets.github_token }}
46-
auto_fix: ${{ github.event_name == 'pull_request' }}
47-
black: true
48-
black_dir: backend
49-
black_command_prefix: poetry run
50-
black_auto_fix: true
51-
45+
args: "format --diff"
46+
src: "./backend"
47+
5248
- name: Run ruff
5349
uses: chartboost/ruff-action@v1
5450
with:

.vscode/extensions.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"ms-vscode-remote.remote-containers",
44
"kokakiwi.vscode-just",
55
"ms-python.python",
6-
"ms-python.black-formatter",
76
"charliermarsh.ruff",
87
"prisma.prisma",
98
"bradlc.vscode-tailwindcss",

.vscode/settings.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,12 @@
4242
// Black + Ruff
4343
"python.formatting.provider": "none",
4444
"[python]": {
45-
"editor.defaultFormatter": "ms-python.black-formatter",
45+
"editor.defaultFormatter": "charliermarsh.ruff",
4646
"editor.formatOnSave": true,
4747
"editor.codeActionsOnSave": {
4848
"source.fixAll": "explicit",
4949
"source.organizeImports": "explicit"
50-
},
51-
// Prevents ruff-vscode#128
52-
"source.organizeImports.isort": true,
50+
}
5351
},
5452
"ruff.args": [
5553
"--config=/workspaces/planner/backend/pyproject.toml"

README.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ Sigue en la sección [Desarrollo general](#desarrollo-general).
5959

6060
Una vez listo, podrás entrar a la app en [http://localhost:3000](http://localhost:3000) 🎉
6161

62-
63-
Necesitaras un nombre de usuario para acceder a CAS. Puedes acceder con `testuser` o con otros usuarios definidos en `cas-mock-users.json`.
64-
62+
Necesitaras un nombre de usuario para acceder a CAS. Puedes acceder con `testuser` o con otros usuarios definidos en `cas-mock-users.json`.
6563

6664
Para realizar acciones sobre el repositorio (migraciones, generación de código, etc) puedes usar:
65+
6766
- el task runner de VSCode (<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> -> _"Tasks: Run Task"_).
6867
- `just` en la linea de comandos. Para ver comandos disponibles, corre `just` desde cualquier carpeta.
6968

7069
Es importante que cuando:
70+
7171
- Cambias la estructura de la API, corras la tarea _"Generate client"_ (también disponible en modo watch).
7272
- Cambies el esquema de la base de datos, corras la tarea _"Create/apply migrations"_ para que los cambios se reflejen en la base de datos.
7373

@@ -89,6 +89,7 @@ El proyecto se integra con dos servicios externos: SIDING (para acceder a mallas
8989
## Deployment
9090

9191
### Deployment automático
92+
9293
Para deployments a producción, se provee un playbook de [Ansible](https://docs.ansible.com/ansible/latest/index.html)
9394
que permite levantar la app completa de principio a fin, pensado para su uso en un entorno de producción, en particular
9495
en un servidor corriendo [Rocky Linux 9](https://rockylinux.org/).
@@ -102,12 +103,12 @@ $ ansible-playbook infra/playbook.yml -i <IP>, -u <USUARIO>
102103
Asumiendo que ya se tenga una llave SSH al servidor. El playbook es completamente idempotente, por lo que
103104
también puede ser utilizado para corregir configuration drift en un servidor de producción.
104105

105-
El playbook opcionalmente provee prompts para entregar credenciales de Tailscale, Netdata y SIDING. Por defecto
106+
El playbook opcionalmente provee prompts para entregar credenciales de Tailscale, Netdata y SIDING. Por defecto
106107
el playbook no levanta un mock de CAS, que es necesario para instalaciones sin acceso al SSO UC.
107108

108109
También se provee un script más ligero en [just](./justfile), que principalmente está pensado para su uso en
109110
junto al sistema de [Continuous Deployment](./.github/workflows/deploy.yml) del repositorio. Sin embargo,
110-
dado un entorno ya configurado de Docker Compose, este puede ser utilizado para levantar un servidor independiente,
111+
dado un entorno ya configurado de Docker Compose, este puede ser utilizado para levantar un servidor independiente,
111112
incluyendo uno que utilice un SSO mock.
112113

113114
Abajo se entregan instrucciones manuales de deploy.
@@ -117,17 +118,20 @@ Abajo se entregan instrucciones manuales de deploy.
117118
El ambiente de staging está diseñado para testear las nuevas versiones del planner en un ambiente real antes de pasar a producción.
118119

119120
En primer lugar, es necesario generar manualmente los archivos `.env` y reemplazar los valores según corresponda para cada servicio utilizando los ejemplos ubicados en cada carpeta:
121+
120122
- _API_`backend/.env.staging.template`
121123
- _servidor web_`frontend/.env.staging.template`
122124
- _base de datos_`database/.env.staging.template`
123125

124126
Luego, se debe crear el volumen de Caddy con `docker volume create caddy_data`.
125127

126128
Ahora, para correr la aplicación utilizando un servidor mock de **CAS externo** se debe:
129+
127130
1. Definir las variables `CAS_SERVER_URL` y `CAS_LOGIN_REDIRECTION_URL` en `backend/.env` con la URL del servidor externo.
128131
2. Levantar los contenedores con `docker compose up planner -d --build` desde la raíz del repositorio.
129132

130133
Alternativamente, para correr la aplicación utilizando un servidor mock de **CAS local**:
134+
131135
1. Dejar las variables `CAS_SERVER_URL` y `CAS_LOGIN_REDIRECTION_URL` en `backend/.env` con los valores predeterminados del archivo de ejemplo `.env.staging.template`.
132136
2. Luego, es necesario generar el archivo `cas-mock-users.json` en `cas-mock/data` a partir del ejemplo `cas-mock-users.json.example`.
133137
3. Levantar los contenedores con `docker compose up -d --build` desde la raíz del repositorio.
@@ -137,22 +141,24 @@ Finalmente, se puede detener la app con `docker compose down` desde la raíz del
137141
### Deployment manual: Producción
138142

139143
El ambiente de producción es manejado por la universidad de forma interna, por lo que aquí se detallan las **instrucciones para desplegar el planner** de forma manual:
140-
1. Se deben crear tres archivos `.env`, uno por cada servicio y dentro de su respectiva carpeta:
141-
- `backend/.env` a partir del ejemplo `backend/.env.production.template` (_API_)
142-
- `frontend/.env` a partir del ejemplo `frontend/.env.production.template` (_servidor web_).
143-
- `database/.env` a partir del ejemplo `database/.env.production.template` (_base de datos_).
144+
145+
1. Se debe crear el archivo `.env` para la API. En particular, crear `backend/.env` a partir del ejemplo `backend/.env.production.template`.
146+
144147
2. Reemplazar los valores de las variables de entorno según corresponda en todos los archivos `.env` creados. **IMPORTANTE:** no olvidar modificar la variable `JWT_SECRET` en `backend/.env` y otras variables que puedan contener secretos para evitar vulnerabilidades de seguridad.
148+
145149
- Para generar una clave `JWT_SECRET` segura y aleatoria se puede utilizar el comando `openssl rand -base64 32`.
150+
146151
3. Se debe crear el volumen donde Caddy guardará los certificados SSL con `docker volume create caddy_data`.
147152
4. Levantar los contenedores con `docker compose up planner -d --build` desde la raíz del repositorio. Requiere _Docker_ y _Docker Compose_ instalados en la máquina.
148153
5. Revisar el estado de los contenedores con `docker ps` o `docker container ls`.
149154
6. Finalmente, se puede detener la app con `docker compose down` desde la misma ubicación.
150155

151-
Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo y versión de *Docker Compose*. En particular, podría ser necesario utilizar `docker-compose` en vez de `docker compose` y `sudo docker compose` en vez de `docker compose`.
156+
Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo y versión de _Docker Compose_. En particular, podría ser necesario utilizar `docker-compose` en vez de `docker compose` y `sudo docker compose` en vez de `docker compose`.
152157

153158
---
154159

155160
## Equipo
161+
156162
<table>
157163
<tbody>
158164
<tr>
@@ -165,7 +171,6 @@ Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo
165171
</tbody>
166172
</table>
167173

168-
169174
Además del equipo núcleo, nos apoyan los contribuidores al proyecto. Puedes ver [la lista completa de contribuidores aquí.](contributors.md)
170175

171176
## Licencia

backend/.env.default

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
################################################################################################
2+
# No es necesario modificar este archivo durante el proceso de deploy. #
3+
# #
4+
# Nota para los desarrolladores: #
5+
# Aquí van las variables de entorno para producción que NO contienen secretos pero requieren #
6+
# ser definidas antes de levantar el contenedor. Si una variable se utiliza en el código, #
7+
# entonces debe ser definida en el archivo `settings.py` en vez de aquí. #
8+
# Para desarrollo, se sobreescriben estas variables en el archivo `.env`. #
9+
################################################################################################
10+
11+
# Variable requerida para configurar Prisma.
12+
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres?schema=public
13+
14+
# Variable requerida para definir el ambiente de ejecución.
15+
PYTHON_ENV=production

backend/.env.development.template

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
# In development mode, use a local server instead of the production URL.
13
PLANNER_URL="http://localhost:3000"
24

35
# URL to the CAS server endpoint.
@@ -7,11 +9,15 @@ PLANNER_URL="http://localhost:3000"
79
# - The user's browser is redirected here when they request to log in.
810
# If the URL the user is redirected to is different to the URL used to verify the
911
# tokens, the `CAS_LOGIN_REDIRECTION_URL` variable should be set to override it.
12+
#
13+
# In development, use a local CAS mock server.
1014
CAS_SERVER_URL="http://localhost:3004/"
1115
CAS_LOGIN_REDIRECTION_URL=""
1216

1317
# Admin RUT. Is always the only admin and has the power of adding/removing mods.
1418
# There will be no admins if this string is left empty.
19+
#
20+
# In development, make an invalid test RUT be the admin.
1521
ADMIN_RUT="012345678-K"
1622

1723
# JWT secret string. If this secret is leaked, anyone can forge JWT tokens for
@@ -31,6 +37,9 @@ SIDING_PASSWORD=""
3137
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
3238
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public"
3339

40+
# In development, connect with redis through localhost instead of through the docker network.
41+
REDIS_URI="redis://localhost:6379"
42+
3443
# Avoid updating data as much as possible in development mode.
3544
AUTOSYNC_COURSES="false"
3645
AUTOSYNC_CURRICULUMS="false"

backend/.env.production.template

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
# Este archivo está pensado para ser copiado con el nombre `.env` y ser usado en producción. #
33
# Es necesario entregar los valores reales a cada una de las variables definidas abajo #
44
# antes de levantar los contenedores de la aplicación para que funcione correctamente. #
5+
# #
6+
# Nota para los desarrolladores: #
7+
# La idea es que este archivo sea completamente estático, ya que será manejado por la #
8+
# universidad directamente en la máquina de producción. Por esta razón, solo debe ser usado #
9+
# para definir secretos. Cualquier otra variable (e.g. URL de la bbdd) se ingresa a nivel de #
10+
# código para poder ser modificada con un commit al repo. #
11+
# En particular, las variables del backend usadas en código se definen como predeterminadas #
12+
# en el archivo `settings.py`, mientras que Las variables necesarias para levantar los #
13+
# contenedores van en el archivo `.env.default`. #
514
###############################################################################################
615

7-
# URL que apunta al planner.
8-
# --> ejemplo: "https://plan.ing.puc.cl"
9-
#
10-
# NOTA: Incluir `https://` en este URL, de otra manera el token se mandará por HTTP desencriptado.
11-
# PLANNER_URL=""
12-
13-
# URL que apunta al servidor de autenticacion CAS.
14-
# --> ejemplo: "https://sso.uc.cl/cas"
15-
# CAS_SERVER_URL=""
16-
1716
# RUT del administrador.
1817
# Es único y tiene el poder de añadir y remover moderadores.
1918
# Si se deja vacío no existirá administrador.
20-
# --> ejemplo: "012345678-K"
19+
# --> ejemplo: "12345678-K"
2120
# ADMIN_RUT=""
2221

2322
# Secreto para generar y verificar tokens JWT.
@@ -26,16 +25,5 @@
2625
# JWT_SECRET="mal secreto, REEMPLAZAR ESTO por un buen secreto."
2726

2827
# Credenciales para utilizar el webservice de Siding.
29-
# SIDING_USERNAME="cambiar_esta_variable"
30-
# SIDING_PASSWORD="cambiar_esta_variable"
31-
32-
# No debería ser necesario modificar esta variable, a menos que se modifiquen las credenciales
33-
# de la base de datos. En tal caso considerar la siguiente estructura:
34-
# "postgresql://USER:PASSWORD@HOST:PORT/DATABASE"
35-
DATABASE_URL="postgresql://postgres:postgres@db:5432/postgres?schema=public"
36-
37-
# No debería ser necesario modificar esta variable.
38-
REDIS_URI="redis://redis:6379/0"
39-
40-
# Don't remove
41-
PYTHON_ENV="production"
28+
# SIDING_USERNAME="<usuario de siding>"
29+
# SIDING_PASSWORD="<contrasena de siding>"

backend/.env.staging.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# URL que apunta al planner.
2-
PLANNER_URL="http://localhost"
2+
PLANNER_URL="https://mallastest.ing.uc.cl"
33

44
# URL que apunta al servidor de autenticacion CAS.
55
CAS_SERVER_URL="http://cas_mock_server:3004/"

backend/Dockerfile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,15 @@ COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt
4242
# Install deps from the requirements-stage
4343
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
4444

45-
# Copy the application code
45+
# TODO: copy only the necessary files
46+
# Copy the application code, including config and env files
4647
COPY ./ /app
4748
WORKDIR /app
4849

49-
# Copy the .env file
50-
COPY .env /app/.env
51-
5250
# Generate the Prisma client
5351
RUN prisma generate
5452

5553
ENV PRE_START_PATH /app/scripts/prestart.sh
5654

5755
# Monitor the app
58-
HEALTHCHECK --start-period=5m CMD curl -f http://localhost:80/health || exit 1
56+
HEALTHCHECK --start-period=5m CMD curl -f http://localhost:80/health || exit 1

backend/app/main.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import asyncio
22
import logging
33
import random
4-
from typing import TYPE_CHECKING, Annotated, Literal
4+
from typing import Literal
55

66
import sentry_sdk
77
from fastapi import FastAPI, HTTPException
88
from fastapi.middleware.cors import CORSMiddleware
99
from fastapi.middleware.gzip import GZipMiddleware
10-
from fastapi.params import Depends
1110
from fastapi.routing import APIRoute
1211
from pydantic import BaseModel, Field
1312
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
@@ -21,11 +20,6 @@
2120
from app.sync.siding.client import client as siding_soap_client
2221
from app.sync.siding.client import get_titles
2322

24-
if TYPE_CHECKING:
25-
from typing import Any
26-
27-
from redis.asyncio import Redis
28-
2923

3024
# Set up operation IDs for OpenAPI
3125
def custom_generate_unique_id(route: APIRoute):
@@ -128,7 +122,7 @@ class HealthResponse(BaseModel):
128122

129123

130124
@app.get("/health")
131-
async def health(redis: Annotated["Redis[Any]", Depends(get_redis)]) -> HealthResponse:
125+
async def health() -> HealthResponse:
132126
response = HealthResponse()
133127

134128
try:
@@ -139,9 +133,10 @@ async def health(redis: Annotated["Redis[Any]", Depends(get_redis)]) -> HealthRe
139133
logging.error(f"Database error detected: {e}")
140134

141135
try:
142-
# Check Redis connection
143-
await redis.ping()
144-
response.detail["redis"] = "healthy"
136+
async with get_redis() as redis:
137+
# Check Redis connection
138+
await redis.ping()
139+
response.detail["redis"] = "healthy"
145140
except Exception as e: # noqa: BLE001
146141
logging.error(f"Redis error detected: {e}")
147142

backend/app/plan/courseinfo.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
from app.plan.validation.courses.logic import Expr
1818

1919

20+
class ExprRedefine(BaseModel):
21+
"""
22+
Type adapter. When we update to Pydantic 2, use `TypeAdapter` instead.
23+
"""
24+
25+
__root__: Expr
26+
27+
2028
class CourseDetails(BaseModel):
2129
# The unique code identifying this course.
2230
code: str
@@ -51,12 +59,12 @@ class CourseDetails(BaseModel):
5159
@staticmethod
5260
def from_db(db: Course) -> "CourseDetails":
5361
# Parse and validate dep json
54-
deps = pydantic.parse_raw_as(Expr, db.deps)
62+
deps = pydantic.parse_raw_as(ExprRedefine, db.deps)
5563
return CourseDetails(
5664
code=db.code,
5765
name=db.name,
5866
credits=db.credits,
59-
deps=deps,
67+
deps=deps.__root__,
6068
banner_equivs=db.banner_equivs,
6169
canonical_equiv=db.canonical_equiv,
6270
program=db.program,

0 commit comments

Comments
 (0)