Skip to content

Commit

Permalink
Added Events-functionality, first iteration (#218)
Browse files Browse the repository at this point in the history
* Have started to work on a new documents-page. I will have to figure out how to import the module to the app

* tmp

* Have begun working on the work signup form

* added the slider, will have to work on it...

* I have added radio-buttons as a selection to the form. Still need to figure out why the slider does not move though

* Getting there with the sliders now

* Design of signup form is basically finished, have to implement a function that retrieves user data from API

* Added logic to make sure only necessary data is sent upon submission

* Added new Component that will be used to display an events

* Added error messages to signup form. Am trying to include the signup form in eventviews for people who are logged in

* Eventview is nearly done. Have to edit the events-page so it can display events side-by-side to save space. Also have to implement the work signup-list

* Patched eventview design a little

* added logic check to signup form

* added API-fetcher to the signup-form so that everytime a signup-form is opened, the user short_uuid can be fetched

* Tried passing loggedIn all the way to the eventViews...big mistake XD Have reverted back to using hasLoginCookie instead, it works the best

* Added view signup list-component, am working on it....

* Have to figure out why the list signups is not rendering when button is clicked...

* Have to fix the .filter-function

* Have to fix the background colours for signup list

* Still have problems with listing the signups (with dummy data) on the list form

* Fixed up some of the views used for the events-functionality using media rules

* Eventview now better optimized for mobile

* Added new listable Item for signups

* Tried to fix upp the show signups-view, but without success. The render-method in the file seems to be the problem

* Added comments on the listables-page

* Refactored the code for the view signups layouts, and components

* Merged new contents of master into branch

* Redesigned the master menu - it looks a lot better already :P

* No more errors when running, still need to create migration

* reverted changes to migration script

* changed autogen script to assume that poetry shell is used

* refactor: Use poetry shell for generating migrations instead of poetry env hack

* added end date field

* Further redesign of master menu. In addition, the approve member-function seems to actually send a request and approve a member again

* Made the master menu look even nicer, and optimized for mobile

* Ignore

* Added files and Routes for Create Event- and Edit Event-page

* Added buttons to link to the new pages

* Added add event button to events-layout

* Created the initial form-design for creating new events

* Almost finished with the create event-form. The view-signups-component design is now complete, where content will be scrollable to make everything more neat

* Added logic to work signupform, along with some more design-refactors of view signups-layout

* feat: Add migrations for new events table

* tmp

* pull

* changed design of view signups-layout, made each signup be split into two rows of details

* Added pages for Admin event-management

* Added API-calls

* Merged backend work on events with bank_events

* Adapted event forms and views for the current fields that are present in the backend

* Prepared events-page for getting the events and their data from the database

* Changed event-model to reflect what information will be required to have for the frontend

* Added separate date and time columns to event-model, adapted schemas, routers and crud

* fixed datatypes in event model

* Added delete endpoint for events, modified Event-page, EventView and EventForm to retreive data via API-call

* Fixed mastermenu admin view for editing/deleting events

* Added master permissions to delete event by uuid

* Events can now be created by members, and can be viewed in the Events-page. There are still problems with displaying the events for public viewers

* Fixed delete event endpoint, working on making it work in the master menu

* Masters can now successfully delete events via the master menu. I have added alerts to notify users for when there are no events, and when an event has been created, deleted, etc.

* Added alert for evenform

* Different user statuses now receive different events based on visibility

* Cleaned upp console logs and added extra checks to event form. First iteration of events ready for release

---------

Co-authored-by: Sebbe <[email protected]>
Co-authored-by: Axel Karlsson <[email protected]>
Co-authored-by: Justin Lex-Hammarskjöld <[email protected]>
  • Loading branch information
4 people authored Aug 2, 2023
1 parent d5997bd commit 4fef612
Show file tree
Hide file tree
Showing 38 changed files with 1,636 additions and 40 deletions.
6 changes: 4 additions & 2 deletions back/db_migrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ and run Alembic outside of a container on your local computer.
I have made a bash script to do this, which port-forwards the database in the dev environment to localhost and runs Alembic.
In order to run this script, you must have the dev environment running,
and you need to have the poetry and the backend installed in a virtual environment on your local machine,
This can be done by running `poetry install` with `back/` as your working directory and then activating the venv it creates.
To run the script, run `scripts/autogen_migrations.sh` with `back/db_migrations` as your working directory.
This can be done by running `poetry install` with `back/` as your working directory,
and then activating the venv in your shell with `poetry shell`.
Then, once you're in the poetry shell,
you can run the script by running `scripts/autogen_migrations.sh` with `back/db_migrations` as your working directory.

If you are creating a new model, make sure that the model is imported in the module file models/__init__.py,
so that the model is initialized when running Alembic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""fix event column datatypes
Revision ID: 584e48977f1c
Revises: a7ed421f8063
Create Date: 2023-07-27 10:50:42.422240
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '584e48977f1c'
down_revision = 'a7ed421f8063'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_start', sa.DateTime(timezone=True), nullable=False))
op.add_column('events', sa.Column('event_end', sa.DateTime(timezone=True), nullable=True))
op.drop_column('events', 'event_date_start')
op.drop_column('events', 'event_owner')
op.drop_column('events', 'event_time_start')
op.drop_column('events', 'event_date_end')
op.drop_column('events', 'event_time_end')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_time_end', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('events', sa.Column('event_date_end', sa.DATE(), autoincrement=False, nullable=True))
op.add_column('events', sa.Column('event_time_start', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('events', sa.Column('event_owner', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('events', sa.Column('event_date_start', sa.DATE(), autoincrement=False, nullable=False))
op.drop_column('events', 'event_end')
op.drop_column('events', 'event_start')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""add date and time columns to events
Revision ID: 8a1190b1d69e
Revises: f1f829d8b88c
Create Date: 2023-07-27 01:08:25.198408
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '8a1190b1d69e'
down_revision = 'f1f829d8b88c'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_date', sa.Date(), nullable=False))
op.drop_column('events', 'event_time')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_time', sa.DATE(), autoincrement=False, nullable=False))
op.drop_column('events', 'event_date')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""fix add date and time columns to events
Revision ID: a7ed421f8063
Revises: 8a1190b1d69e
Create Date: 2023-07-27 01:49:26.796877
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'a7ed421f8063'
down_revision = '8a1190b1d69e'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_date_start', sa.Date(), nullable=False))
op.add_column('events', sa.Column('event_time_start', sa.String(), nullable=False))
op.add_column('events', sa.Column('event_date_end', sa.Date(), nullable=True))
op.add_column('events', sa.Column('event_time_end', sa.String(), nullable=True))
op.drop_column('events', 'event_date')
op.drop_column('events', 'event_endtime')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_endtime', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True))
op.add_column('events', sa.Column('event_date', sa.DATE(), autoincrement=False, nullable=False))
op.drop_column('events', 'event_time_end')
op.drop_column('events', 'event_date_end')
op.drop_column('events', 'event_time_start')
op.drop_column('events', 'event_date_start')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""modify events table
Revision ID: f1f829d8b88c
Revises: fb9f5b398c71
Create Date: 2023-07-26 20:18:43.135873
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'f1f829d8b88c'
down_revision = 'fb9f5b398c71'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('event_owner', sa.String(), nullable=False))
op.add_column('events', sa.Column('event_endtime', sa.DateTime(timezone=True), nullable=True))
op.drop_index('ix_events_short_uuid', table_name='events')
op.drop_column('events', 'sign_up_end_time')
op.drop_column('events', 'short_uuid')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('events', sa.Column('short_uuid', sa.VARCHAR(), sa.Computed('replace(replace(encode(decode("left"(replace(((uuid)::character varying)::text, \'-\'::text, \'\'::text), 12), \'hex\'::text), \'base64\'::text), \'+\'::text, \'-\'::text), \'/\'::text, \'_\'::text)', persisted=True), autoincrement=False, nullable=True))
op.add_column('events', sa.Column('sign_up_end_time', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False))
op.create_index('ix_events_short_uuid', 'events', ['short_uuid'], unique=False)
op.drop_column('events', 'event_endtime')
op.drop_column('events', 'event_owner')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""add events table
Revision ID: fb9f5b398c71
Revises: 8b45108e5c9d
Create Date: 2023-07-16 22:58:11.766419
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'fb9f5b398c71'
down_revision = '8b45108e5c9d'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('events',
sa.Column('uuid', sa.UUID(), nullable=False),
sa.Column('short_uuid', sa.String(), sa.Computed("replace(replace(encode(decode(left(replace(CAST(uuid AS VARCHAR), '-', ''), 12), 'hex'), 'base64'), '+', '-'), '/', '_')", ), nullable=True),
sa.Column('time_created', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('time_updated', sa.DateTime(timezone=True), nullable=True),
sa.Column('event_time', sa.Date(), nullable=False),
sa.Column('sign_up_end_time', sa.DateTime(timezone=True), nullable=False),
sa.Column('title', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.Column('location', sa.String(), nullable=False),
sa.Column('visibility', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('uuid')
)
op.create_index(op.f('ix_events_short_uuid'), 'events', ['short_uuid'], unique=True)
op.create_index(op.f('ix_events_uuid'), 'events', ['uuid'], unique=False)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_events_uuid'), table_name='events')
op.drop_index(op.f('ix_events_short_uuid'), table_name='events')
op.drop_table('events')
# ### end Alembic commands ###
3 changes: 1 addition & 2 deletions back/db_migrations/scripts/autogen_migrations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ export SQLALCHEMY_DATABASE_URL='postgresql+asyncpg://tmeit_backend:HBXOHEc6Tpkqu
echo "Enter a short comment about what you're changing in this migration"
read -r COMMENT

VENV_PATH=$(. cd .. && poetry env info --path)
"$VENV_PATH"/bin/alembic -c alembic.ini revision --autogenerate -m "$COMMENT"
alembic -c alembic.ini revision --autogenerate -m "$COMMENT"
2 changes: 1 addition & 1 deletion back/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tmeit_backend"
version = "1.1.2"
version = "1.2.0"
description = "Python backend for the TMEIT website"
authors = ["TraditionsMEsterIT"]
license = "AGPL-3.0"
Expand Down
3 changes: 3 additions & 0 deletions back/tmeit_backend/app_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .routers.login import router as login_router
from .routers.sign_up import router as sign_up_router
from .routers.test_email import router as test_email_router
from .routers.events import router as event_router


# Our FastAPI sub-app for the v1 API
app = FastAPI()
Expand All @@ -19,3 +21,4 @@
app.include_router(version_router)
app.include_router(sign_up_router, prefix="/sign_up")
app.include_router(test_email_router)
app.include_router(event_router, prefix="/events")
2 changes: 2 additions & 0 deletions back/tmeit_backend/app_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
@app.get("/about")
@app.get("/documents")
@app.get("/passres")
@app.get("/createEvent")
@app.get("/editEvent")
async def load_js_app():
return FileResponse('static/front/index.html')

Expand Down
57 changes: 57 additions & 0 deletions back/tmeit_backend/crud/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import datetime

from typing import TypeVar, Type
from uuid import UUID, uuid4

from pydantic import BaseModel
from sqlalchemy import update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select

from .. import models
from ..models.events import Event
from ..schemas.events import EventMemberCreate, EventMemberView, EventMemberPatch

S = TypeVar('S', bound=BaseModel)

async def create_event(db: AsyncSession, data: EventMemberCreate) -> EventMemberView:
uuid = uuid4()
async with db.begin():
db.add_all([
Event(uuid=str(uuid), **data.dict()),
])
return await get_event(db=db, uuid=uuid, response_schema=EventMemberView)

async def get_event(db: AsyncSession, uuid: UUID, response_schema: Type[S]) -> S:
stmt = select(models.Event).where(models.Event.uuid == str(uuid))
result = (await db.execute(stmt)).fetchone()
if result is None:
raise KeyError()
sql_event = dict(result.Event.__dict__)
return response_schema.parse_obj(sql_event)

async def get_events_publicuser(db: AsyncSession, response_schema: Type[S], skip: int = 0, limit: int = 1000) -> list[S]:
stmt = select(models.Event).where(models.Event.visibility == str("public")).offset(skip).limit(limit)
result = await db.execute(stmt)
sql_events = [dict(e.__dict__) for e in result.scalars().all()]
return [response_schema.parse_obj(sql_event) for sql_event in sql_events]

async def get_events_internaluser(db: AsyncSession, response_schema: Type[S], skip: int = 0, limit: int = 1000) -> list[S]:
stmt = select(models.Event).where(models.Event.visibility != str("elected")).offset(skip).limit(limit)
result = await db.execute(stmt)
sql_events = [dict(e.__dict__) for e in result.scalars().all()]
return [response_schema.parse_obj(sql_event) for sql_event in sql_events]

async def get_events_electeduser(db: AsyncSession, response_schema: Type[S], skip: int = 0, limit: int = 1000) -> list[S]:
stmt = select(models.Event).offset(skip).limit(limit)
result = await db.execute(stmt)
sql_events = [dict(e.__dict__) for e in result.scalars().all()]
return [response_schema.parse_obj(sql_event) for sql_event in sql_events]

async def remove_event(db: AsyncSession, uuid: UUID) -> None:
async with db.begin():
stmt = select(models.Event).where(models.Event.uuid == str(uuid))
result = (await db.execute(stmt)).fetchone()
if result is None:
raise KeyError()
await db.delete(result.Event)
2 changes: 1 addition & 1 deletion back/tmeit_backend/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# so we import base through here so everything gets picked up
from ..database import Base


from .events import Event
from .members.members import Member
from .members.website_migration import MemberWebsiteMigration
from .sign_up import SignUp
54 changes: 54 additions & 0 deletions back/tmeit_backend/models/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from sqlalchemy import Column, String, Computed, Date, DateTime, ForeignKey, Table
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, relationship
from sqlalchemy.sql.functions import func

from typing import TYPE_CHECKING, List

from ._utils import short_uuid_from_uuid # reusing the functionality from the members module
from ..database import Base
from .members.members import Member

"""
Table for creating a many-to-many relationship between useres and events
"""
#attending = Table(
# "attending",
# Base.metadata,
# Column("event", ForeignKey("events.uuid"), primary_key=True),
# Column("users", ForeignKey("members.uuid"), primary_key=True),
# )


class Event(Base):
"""
SQL Alchemy model for events.
Every event has a unique ID as well as several data fields.
Currently events can be updated by every TMEIT member.
"""

# Table containing event records
__tablename__ = "events"

# Primary key, 128 bit ID
uuid = Column(UUID, primary_key=True, index=True)

# Created/Updated timestamps
time_created = Column(DateTime(timezone=True), server_default=func.now())
time_updated = Column(DateTime(timezone=True), onupdate=func.now())

event_start = Column(DateTime(timezone=True), nullable=False)
event_end = Column(DateTime(timezone=True))

# sign_up_end_time = Column(DateTime(timezone=True), nullable=False)

title = Column(String, nullable=False)
description = Column(String, nullable=False)

location = Column(String, nullable=False)

visibility = Column(String, nullable=False) # Who can see the event. (Public, prao or marsalk + masters + vrak)
# Following line should be line below in declarative form, not sure how to achieve that
# children: Mapped[List[Child]] = relationship(secondary=association_table)
# attending = relationship("Member", secondary=attending)

Loading

0 comments on commit 4fef612

Please sign in to comment.