Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • sbibauw/languagelab
1 résultat
Afficher les modifications
Validations sur la source (25)
Affichage de
avec 447 ajouts et 36 suppressions
Fichier ajouté
......@@ -41,7 +41,31 @@ docker_deploy:
stage: deploy
only:
- main
- feat/cd
image: alpine:latest
script:
- echo "/mnt/data/languagelab/repo/scripts/update.sh" > /mnt/pipeline
build-dev:
stage: build
only:
- dev
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
parallel:
matrix:
- COMPONENT: [frontend, backend]
script:
- docker build -t $CI_REGISTRY/sbibauw/languagelab:${COMPONENT}-dev $COMPONENT
- docker push $CI_REGISTRY/sbibauw/languagelab:${COMPONENT}-dev
docker_deploy-dev:
stage: deploy
only:
- dev
- feat/cd
image: alpine:latest
script:
- echo "/mnt/data/languagelab/repo/scripts/update.dev.sh" > /mnt/pipeline
......@@ -73,6 +73,10 @@ The CI run `npm run lint` and `black --check --verbose` on every commit, on ever
git config --local core.hooksPath .githooks
```
#### up-to-date PR
The project is more and more complex, integrated, tested, etc. To avoid conflicts, ensure you're PR is always up-to-date with the `dev` branch.
## Useful tips
#### Users
......@@ -90,3 +94,49 @@ After an update, it may happen that the dependencies have changed. In such case,
#### API documentation
To use, try and explore the backend's API, both production and development's server have the `/docs` endpoint. Using the interface, it's possible to register, login, get, create, update, ...
#### Alembic
As alembic is backend-specific, you have to go into the `backend` folder for the commands to work. Note that the alembic environment is slighly different in local, dev and prod, so they all have their own `alembic.*.ini` file. Those files shouldn't be changed unless you really know what you are doing.
:warning: Alembic versions work as a linked list. Each version refer it's previous and next version. To prevent having to tweak "weird" things, ensure you're up-to-date with any other version update. You could need to redo those steps if someone else merged a change in the meantime.
To create a migration script, you can run
```sh
alembic revision -m "<change message>"
```
It will tell you the name of the new file, where you can implement the changes.
In most cases, you should only need to change the functions:
- `upgrade` contains all your changes
- `downgrade` drop them. This is **deeply advised** to allow to rollback in case of issue, especially in production.
Here are the most useful alembic functions:
```python
# Create a table
op.create_table(
'account',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('name', sa.String(50), nullable=False),
)
# Drop a table
op.drop_table('account')
# Add a column
op.add_column('account', sa.Column('last_transaction_date', sa.DateTime))
# Remove a column
op.drop_column('account', 'last_transaction_date')
# Rename a column
op.alter_column('account', 'id', new_column_name='uuid')
```
To update to the latest version:
```sh
alembic upgrade head
```
:warning: You will also need to run that if someone else wrote a version. This is NOT automatic for the local environment.
For more in depth information, check the [official documentation](https://alembic.sqlalchemy.org/en/latest/).
# Instructions pour les tuteurs
## Section xxxx
- pas clair : ????
## Foire aux questions
### Pourquoi il n'y pas de réactions/likes (👍) sur les messages ?
Parce qu'on veut vous faire parler.
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os
sqlalchemy.url = sqlite:///languagelab.dev.sqlite
[post_write_hooks]
hooks = black
black.type = console_scripts
black.entrypoint = black
black.options = -l 79
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os
sqlalchemy.url = sqlite:////mnt/data/languagelab/backend/db.dev.sqlite3
[post_write_hooks]
hooks = black
black.type = console_scripts
black.entrypoint = black
black.options = -l 79
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
......@@ -10,6 +10,9 @@ sqlalchemy.url = sqlite:///languagelab.sqlite
[post_write_hooks]
hooks = black
black.type = console_scripts
black.entrypoint = black
black.options = -l 79
[loggers]
keys = root,sqlalchemy,alembic
......
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os
sqlalchemy.url = sqlite:////mnt/data/languagelab/backend/db.sqlite3
[post_write_hooks]
hooks = black
black.type = console_scripts
black.entrypoint = black
black.options = -l 79
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
"""Update survey response with other languages
Revision ID: 37f4cc82f93e
Revises:
Revises:
Create Date: 2024-12-22 18:42:42.049100
"""
......
"""Add my_slots column to users table
Revision ID: 9dfb49268c5f
Revises: ce5a922c7fc4
Create Date: 2025-02-23 18:48:24.958552
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "9dfb49268c5f"
down_revision: Union[str, None] = "ce5a922c7fc4"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
op.add_column(
"users", sa.Column("my_slots", sa.JSON, nullable=False, server_default="[]")
)
def downgrade():
op.drop_column("users", "my_slots")
"""Add new columns to user table
Revision ID: ce5a922c7fc4
Revises: fe09c6f768cd
Create Date: 2025-02-23 12:01:52.379157
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "ce5a922c7fc4"
down_revision: Union[str, None] = "fe09c6f768cd"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
op.add_column("users", sa.Column("availabilities", sa.JSON, default=[]))
op.add_column("users", sa.Column("tutor_list", sa.JSON, default=[]))
op.add_column("users", sa.Column("my_tutor", sa.String, default=""))
op.add_column("users", sa.Column("bio", sa.String, default=""))
def downgrade():
op.drop_column("users", "availabilities")
op.drop_column("users", "tutor_list")
op.drop_column("users", "my_tutor")
op.drop_column("users", "bio")
"""Add code column to test_typing table
Revision ID: fe09c6f768cd
Revises: 9038306d44fc
Create Date: 2025-01-31 21:45:56.343739
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "fe09c6f768cd"
down_revision: Union[str, None] = "0bf670c4a564"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"_tmp_test_typing",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("code", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.drop_table("test_typing")
op.rename_table("_tmp_test_typing", "test_typing")
......@@ -263,8 +263,8 @@ def delete_message_feedback(db: Session, feedback_id: int):
db.commit()
def create_test_typing(db: Session, test: schemas.TestTypingCreate, user: schemas.User):
db_test = models.TestTyping(user_id=user.id)
def create_test_typing(db: Session, test: schemas.TestTypingCreate):
db_test = models.TestTyping(code=test.code)
db.add(db_test)
db.commit()
db.refresh(db_test)
......
......@@ -290,28 +290,14 @@ def store_typing_entries(
pass
@usersRouter.post("/{user_id}/tests/typing", status_code=status.HTTP_201_CREATED)
@studyRouter.post("/typing", status_code=status.HTTP_201_CREATED)
def create_test_typing(
user_id: int,
test: schemas.TestTypingCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: schemas.User = Depends(get_jwt_user),
):
if (
not check_user_level(current_user, models.UserType.ADMIN)
and current_user.id != user_id
):
raise HTTPException(
status_code=401,
detail="You do not have permission to create a test for this user",
)
db_user = crud.get_user(db, user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
typing_id = crud.create_test_typing(db, test, db_user).id
typing_id = crud.create_test_typing(db, test).id
if test.entries:
background_tasks.add_task(store_typing_entries, db, test.entries, typing_id)
......
......@@ -14,6 +14,7 @@ from enum import Enum
from database import Base
import datetime
from utils import datetime_aware
from sqlalchemy.dialects.postgresql import JSON
class UserType(Enum):
......@@ -40,7 +41,7 @@ class User(Base):
password = Column(String)
type = Column(Integer, default=UserType.STUDENT.value)
is_active = Column(Boolean, default=True)
availability = Column(String, default=0)
bio = Column(String, default="")
ui_language = Column(String, default="fr")
home_language = Column(String, default="en")
target_language = Column(String, default="fr")
......@@ -48,6 +49,10 @@ class User(Base):
gender = Column(String, default=None)
calcom_link = Column(String, default="")
last_survey = Column(DateTime, default=None)
availabilities = Column(JSON, default=[])
tutor_list = Column(JSON, default=[])
my_tutor = Column(String, default="")
my_slots = Column(JSON, default=[])
sessions = relationship(
"Session", secondary="user_sessions", back_populates="users"
......@@ -181,8 +186,8 @@ class TestTyping(Base):
__tablename__ = "test_typing"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
created_at = Column(DateTime, default=datetime_aware)
code = Column(String)
entries = relationship("TestTypingEntry", backref="typing")
......@@ -287,7 +292,6 @@ class Study(Base):
start_date = Column(DateTime)
end_date = Column(DateTime)
chat_duration = Column(Integer)
typing_test = Column(Boolean)
users = relationship("User", secondary="study_users", back_populates="studies")
surveys = relationship(
......
......@@ -20,7 +20,7 @@ class User(BaseModel):
email: str
nickname: str
type: int
availability: int
bio: str | None
is_active: bool
ui_language: str | None
home_language: str | None
......@@ -29,6 +29,10 @@ class User(BaseModel):
gender: str | None = None
calcom_link: str | None
last_survey: NaiveDatetime | None = None
availabilities: list[dict] | None = []
tutor_list: list[str] | None = []
my_tutor: str | None = None
my_slots: list[dict] | None = []
class Config:
from_attributes = True
......@@ -53,7 +57,7 @@ class UserCreate(BaseModel):
nickname: str | None = None
password: str
type: int = UserType.STUDENT.value
availability: int = 0
bio: str | None = None
is_active: bool = True
ui_language: str | None = None
home_language: str | None = None
......@@ -62,6 +66,10 @@ class UserCreate(BaseModel):
gender: str | None = None
calcom_link: str | None = None
last_survey: NaiveDatetime | None = None
availabilities: list[dict] | None = []
tutor_list: list[str] | None = []
my_tutor: str | None = None
my_slots: list[dict] | None = []
class UserUpdate(BaseModel):
......@@ -69,7 +77,7 @@ class UserUpdate(BaseModel):
nickname: str | None = None
password: str | None = None
type: int | None = None
availability: int | None = None
bio: str | None = None
is_active: bool | None = None
ui_language: str | None = None
home_language: str | None = None
......@@ -78,6 +86,10 @@ class UserUpdate(BaseModel):
gender: str | None = None
calcom_link: str | None = None
last_survey: NaiveDatetime | None = None
availabilities: list[dict] | None = []
tutor_list: list[str] | None = []
my_tutor: str | None = None
my_slots: list[dict] | None = None
class Config:
from_attributes = True
......@@ -225,6 +237,7 @@ class TestTypingEntryCreate(BaseModel):
class TestTypingCreate(BaseModel):
entries: list[TestTypingEntryCreate]
code: str
class TestVocabularyCreate(BaseModel):
......
......@@ -2,6 +2,11 @@ services:
languagelab-frontend:
container_name: languagelab-frontend
image: registry.forge.uclouvain.be/sbibauw/languagelab:frontend
environment:
- PUBLIC_API_URL=https://languagelab.be/tmp-api
- PUBLIC_API_PROXY=https://languagelab.be/tmp-api
- PUBLIC_APP_URL=https://languagelab.be
- PUBLIC_WS_URL=wss://languagelab.be/tmp-api/v1/ws
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
......@@ -12,7 +17,6 @@ services:
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`languagelab.be`)"
#- "traefik.http.routers.frontend.rule=Host(`languagelab.sipr.ucl.ac.be`)"
- "traefik.http.routers.frontend.tls=true"
- "traefik.http.services.frontend.loadbalancer.server.port=8080"
......@@ -23,8 +27,8 @@ services:
- DATABASE_URL=sqlite:////data/db.sqlite3
- JWT_SECRET_KEY=${LANGUAGELAB_JWT_SECRET_KEY}
- JWT_REFRESH_SECRET_KEY=${LANGUAGELAB_JWT_REFRESH_SECRET_KEY}
- ADMIN_EMAIL=${LANGUAGELAB_ADMIN_EMAIL}
- ADMIN_PASSWORD=${LANGUAGELAB_ADMIN_PASSWORD}
- ADMIN_EMAIL=${LANGUAGELAB_ADMIN_EMAIL_DEV}
- ADMIN_PASSWORD=${LANGUAGELAB_ADMIN_PASSWORD_DEV}
- CALCOM_SECRET=${LANGUAGELAB_CALCOM_SECRET}
- ALLOWED_ORIGINS=https://languagelab.be,https://api.languagelab.be
volumes:
......@@ -38,7 +42,50 @@ services:
- "traefik.http.routers.backend.tls=true"
- "traefik.http.services.backend.loadbalancer.server.port=8000"
- "traefik.http.routers.backend.rule=Host(`languagelab.be`) && (PathPrefix(`/tmp-api`) || PathPrefix(`/docs`) || PathPrefix(`/openapi.json`))"
#- "traefik.http.routers.backend.rule=Host(`languagelab.sipr.ucl.ac.be`) && (PathPrefix(`/tmp-api`) || PathPrefix(`/docs`) || PathPrefix(`/openapi.json`))"
languagelab-frontend-dev:
container_name: languagelab-frontend-dev
image: registry.forge.uclouvain.be/sbibauw/languagelab:frontend-dev
environment:
- PUBLIC_API_URL=https://dev.languagelab.be/tmp-api
- PUBLIC_API_PROXY=https://dev.languagelab.be/tmp-api
- PUBLIC_APP_URL=https://dev.languagelab.be
- PUBLIC_WS_URL=wss://dev.languagelab.be/tmp-api/v1/ws
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- languagelab-backend-dev
networks:
- external
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend-dev.rule=Host(`dev.languagelab.be`)"
- "traefik.http.routers.frontend-dev.tls=true"
- "traefik.http.services.frontend-dev.loadbalancer.server.port=8080"
languagelab-backend-dev:
container_name: languagelab-backend-dev
image: registry.forge.uclouvain.be/sbibauw/languagelab:backend-dev
environment:
- DATABASE_URL=sqlite:////data/db.dev.sqlite3
- JWT_SECRET_KEY=${LANGUAGELAB_JWT_SECRET_KEY}
- JWT_REFRESH_SECRET_KEY=${LANGUAGELAB_JWT_REFRESH_SECRET_KEY}
- ADMIN_EMAIL=${LANGUAGELAB_ADMIN_EMAIL}
- ADMIN_PASSWORD=${LANGUAGELAB_ADMIN_PASSWORD}
- CALCOM_SECRET=${LANGUAGELAB_CALCOM_SECRET}
- ALLOWED_ORIGINS=https://dev.languagelab.be,https://api.dev.languagelab.be
volumes:
- /mnt/data/languagelab/backend:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- external
labels:
- "traefik.enable=true"
- "traefik.http.routers.backend-dev.tls=true"
- "traefik.http.services.backend-dev.loadbalancer.server.port=8000"
- "traefik.http.routers.backend-dev.rule=Host(`dev.languagelab.be`) && (PathPrefix(`/tmp-api`) || PathPrefix(`/docs`) || PathPrefix(`/openapi.json`))"
traefik:
container_name: traefik
......
......@@ -3,8 +3,6 @@ node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
......@@ -3,7 +3,8 @@
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "VITE_API_URL=http://127.0.0.1:8000/tmp-api VITE_APP_URL=http://127.0.0.1:5173 VITE_WS_URL=ws://127.0.0.1:8000/tmp-api/v1/ws vite dev --host 127.0.0.1",
"dev": "PUBLIC_API_URL=http://127.0.0.1:8000/tmp-api PUBLIC_APP_URL=http://127.0.0.1:5173 PUBLIC_WS_URL=ws://127.0.0.1:8000/tmp-api/v1/ws vite dev --host 127.0.0.1",
"dev-dev": "PUBLIC_API_URL=https://beta.dev.languagelab.be/tmp-api PUBLIC_APP_URL=http://127.0.0.1:5173 PUBLIC_WS_URL=wss://beta.dev.languagelab.be/tmp-api/v1/ws vite dev --host 127.0.0.1",
"build": "vite build",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
......
......@@ -144,7 +144,8 @@
"participants": "Participants",
"programed": "Scheduled",
"status": "Status",
"topics": "Topics"
"topics": "Topics",
"toggle": "Participants"
}
},
"button": {
......@@ -217,10 +218,36 @@
}
},
"register": {
"confirm": "Confirm",
"bio": "Short Bio",
"bio.note": "Introduce yourself in one sentence",
"bio.ph": "Write something about yourself here...",
"birthyear": "Year of birth",
"birthyear.note": "In what year were you born?",
"confirmPassword": "Confirm password",
"confirmTutor": "Do you confirm selecting “{NAME}” as your guardian?",
"availabilities": "Availabilities",
"noAvailabilities": "No availabilities provided",
"notAvailable": "Not available",
"scheduleMeeting": "Schedule Meeting",
"linkNotAvailable": "Link Not Available",
"noTutorsAvailable": "No tutors are currently available",
"selectWeekday": "Select a weekday",
"weekday": "Weekday",
"startTime": "Start Time",
"endTime": "End Time",
"addAvailability": "Add Availability",
"yourAvailabilities": "Your Availabilities",
"remove": "Remove",
"loadingTutors": "Loading tutors...",
"firstLanguage": "First Language:",
"role": "Role",
"ScheduleWith": "Schedule a meeting with",
"roles": {
"note": "Select the role you want to play in the application",
"learner": "Learner",
"tutor": "Tutor"
},
"consent": {
"intro": "You are invited to participate in a scientific study. \nThe objective of this study is to understand how tutors and foreign language learners interact during online tutoring sessions. \nThe data collected will be used to improve online tutoring tools and to better understand cognitive processes on both sides.",
"ok": "I agree to participate in the study as described above.",
......@@ -290,9 +317,12 @@
"availabilities": "Availability",
"consent": "Consent",
"continue": "Continue",
"timeslots": "tutor selection",
"availableSlots": "Available slots :",
"information": "Information",
"signup": "Registration",
"start": "To start"
"start": "To start",
"ScheduleWith": "Schedule a meeting with"
},
"welcome": "Welcome to LanguageLab! Before you begin, please fill out the following information. This will allow us to get to know you better and tailor the experience to your needs.",
"homeLanguage": "First language",
......@@ -372,7 +402,8 @@
"calcomWarning": "Invalid cal.com link",
"cesttime": "CET time (Brussels)",
"noTutors": "No tutor available. \nPlease select other availabilities.",
"setAvailabilities": "Select your availability"
"setAvailabilities": "Select your availability",
"availableSlots": "Available slots"
},
"users": {
"actions": "Actions",
......