Skip to content

Commit 697b062

Browse files
LittleskXimenaColopyemlozevsWertops
authored
Sprint0 (#25)
* created workshop list and create components and added them eot the navigation * added the model/api stuff * issue #14 - added default workshop data to the database * created a workshop-list service, but it doesnt work * Updated file to be in correct place * added skeleton code for workshop service * created a list function if services/workshops.py so the api lists all of the workshops * implemented getWorkshops() * Retrieve workshop data from database and display workshop title and id data on webpage * Reformat display of workshop list and attributes * Added comments to explain getWorkshop functions * Changed getworkshop to getWorkshops and its references * Adjust workshop initialization & clean up comments in workshop list files * added description, location, and date to workshops * added docs to workshop api * explained args --------- Co-authored-by: XimenaColopy <[email protected]> Co-authored-by: Emma Lozevski <[email protected]> Co-authored-by: Emma Lozevski <[email protected]> Co-authored-by: Wertops <[email protected]> Co-authored-by: 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 <[email protected]>
1 parent c42dc98 commit 697b062

22 files changed

+364
-5
lines changed

backend/api/workshop.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from fastapi import APIRouter, Depends
2+
from ..models import Workshop
3+
from ..services.workshop import WorkshopService
4+
5+
api = APIRouter(prefix="/api/workshop")
6+
7+
#returns list of workshop models. It takes in a workshop_service as a dependency
8+
@api.get("", response_model=list[Workshop], tags=['Workshops'])
9+
def list_workshops(workshop_svc: WorkshopService = Depends()) -> list[Workshop]:
10+
return workshop_svc.list()

backend/entities/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .entity_base import EntityBase
1717
from .user_entity import UserEntity
1818
from .role_entity import RoleEntity
19+
from .workshop_entity import WorkshopEntity
1920
from .permission_entity import PermissionEntity
2021
from .user_role_entity import user_role_table
2122

backend/entities/workshop_entity.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'''User accounts for all registered users in the application.'''
2+
3+
4+
from sqlalchemy import Integer, String, DateTime, ForeignKey
5+
from sqlalchemy.orm import Mapped, mapped_column, relationship
6+
from typing import Self
7+
from .entity_base import EntityBase
8+
from .user_entity import UserEntity
9+
#from .user_role_entity import user_role_table
10+
from ..models import Workshop, User
11+
from datetime import datetime
12+
13+
14+
__authors__ = ['Kris Jordan']
15+
__copyright__ = 'Copyright 2023'
16+
__license__ = 'MIT'
17+
18+
19+
class WorkshopEntity(EntityBase):
20+
__tablename__ = 'workshop'
21+
22+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
23+
title: Mapped[str] = mapped_column(String(64), nullable=False, default='')
24+
description: Mapped[str] = mapped_column(String(500), nullable=False, default='')
25+
location: Mapped[str] = mapped_column(String(64), nullable=False, default='')
26+
date: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now)
27+
28+
#host: Mapped['UserEntity'] = mapped_column(UserEntity, )
29+
host_id: Mapped[int] = mapped_column(ForeignKey('user.id'), nullable=True)
30+
#host: Mapped['UserEntity'] = relationship(back_populates='workshop')
31+
32+
@classmethod
33+
def from_model(cls, model: Workshop) -> Self:
34+
return cls(
35+
id=model.id,
36+
title=model.title,
37+
description=model.description,
38+
location=model.location,
39+
date=model.date,
40+
#host_id = model.host_id
41+
)
42+
43+
def to_model(self) -> Workshop:
44+
return Workshop(
45+
id=self.id,
46+
title=self.title,
47+
description=self.description,
48+
location=self.location,
49+
date=self.date,
50+
#host_id = model.host_id
51+
)
52+
53+

backend/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Entrypoint of backend API exposing the FastAPI `app` to be served by an application server such as uvicorn."""
22

33
from fastapi import FastAPI
4-
from .api import health, static_files, profile, authentication, user
4+
from .api import health, static_files, profile, authentication, user, workshop
55
from .api.admin import users as admin_users
66
from .api.admin import roles as admin_roles
77

@@ -21,6 +21,7 @@
2121
)
2222

2323
app.include_router(user.api)
24+
app.include_router(workshop.api)
2425
app.include_router(profile.api)
2526
app.include_router(health.api)
2627
app.include_router(authentication.api)

backend/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .user import User, ProfileForm, NewUser
66
from .role import Role
77
from .role_details import RoleDetails
8+
from .workshop import Workshop
89

910
__authors__ = ["Kris Jordan"]
1011
__copyright__ = "Copyright 2023"

backend/models/workshop.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Workshop is the data object"""
2+
3+
from pydantic import BaseModel
4+
from . import User
5+
from datetime import datetime
6+
7+
8+
9+
__authors__ = ["Kris Jordan"]
10+
__copyright__ = "Copyright 2023"
11+
__license__ = "MIT"
12+
13+
14+
class Workshop(BaseModel):
15+
id: int | None = None
16+
title: str
17+
description: str | None = "No description provided for this event."
18+
location: str | None = "Location TBD"
19+
date: datetime | None = None
20+
host_id: int | None = None
21+
host: User | None = None

backend/script/dev_data/workshops.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Sample workshops to use in the development environment.
2+
3+
These were intially designed to be used by the `script.reset_database` module."""
4+
5+
from ...models import Workshop
6+
from datetime import datetime
7+
from . import users
8+
9+
10+
workshop1 = Workshop(id=1, title="Workshop1", description="this is a sample description for workshop1", location="Sitterson Hall", date=datetime(2023, 4, 5, 12, 0), host_id=users.merritt_manager.id)
11+
12+
workshop2 = Workshop(id=2, title="Workshop2", description="this is a sample description for workshop2", location="Phillips Hall", date=datetime(2023, 4, 10, 3, 0))
13+
14+
workshop3 = Workshop(id=3, title="Workshop3", description="this is a sample description for workshop3", location="Dey Hall", date=datetime(2023, 4, 12, 4, 30))
15+
16+
workshop4 = Workshop(id=4, title="Workshop4", description="this is a sample description for workshop4", location="Peabody Hall", date=datetime(2023, 4, 20, 1, 20))
17+
18+
models = [
19+
workshop1,
20+
workshop2,
21+
workshop3,
22+
workshop4
23+
]

backend/script/reset_database.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
__copyright__ = "Copyright 2023"
1212
__license__ = "MIT"
1313

14-
1514
if getenv("MODE") != "development":
1615
print("This script can only be run in development mode.", file=sys.stderr)
1716
print("Add MODE=development to your .env file in workspace's `backend/` directory")
@@ -60,4 +59,13 @@
6059
entity.role = session.get(RoleEntity, role.id)
6160
session.add(entity)
6261
session.execute(text(f'ALTER SEQUENCE permission_id_seq RESTART WITH {len(permissions.pairs) + 1}'))
63-
session.commit()
62+
session.commit()
63+
64+
# Add Workshops
65+
with Session(engine) as session:
66+
from .dev_data import workshops
67+
to_entity = entities.WorkshopEntity.from_model
68+
session.add_all([to_entity(model) for model in workshops.models])
69+
session.execute(text(f'ALTER SEQUENCE {entities.WorkshopEntity.__table__}_id_seq RESTART WITH {len(workshops.models) + 1}'))
70+
session.commit()
71+

backend/services/workshop.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from fastapi import Depends
2+
from sqlalchemy import select, or_, func
3+
from sqlalchemy.orm import Session
4+
from ..database import db_session
5+
from ..models import Workshop
6+
from ..entities import WorkshopEntity
7+
#from . import user
8+
from ..entities import UserEntity
9+
#from .permission import PermissionService
10+
11+
#from .permission import PermissionService
12+
13+
14+
class WorkshopService:
15+
16+
_session: Session
17+
#_permission: PermissionService
18+
#_permission: PermissionService
19+
20+
def __init__(self, session: Session = Depends(db_session)):
21+
self._session = session
22+
#self._permission = permission
23+
24+
#self._permission = permission
25+
26+
#def get_host(id: int) -> User:
27+
# query = select(UserEntity).where(WorkshopEntity.id == id)
28+
# user_entity: UserEntity = self._session.scalar(query)
29+
# if user_entity is None:
30+
# return None
31+
# else:
32+
# model = user_entity.to_model()
33+
# model.permissions = self._permission.get_permissions(model)
34+
# return model
35+
36+
37+
def list(self) -> list[Workshop]:
38+
query = select(WorkshopEntity)
39+
workshop_entities: WorkshopEntity = self._session.execute(query).scalars()
40+
return [ workshop_entity.to_model() for workshop_entity in workshop_entities]
41+

frontend/src/app/app-routing.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import { AppTitleStrategy } from './app-title.strategy';
44
import { GateComponent } from './gate/gate.component';
55
import { HomeComponent } from './home/home.component';
66
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
7+
import { WorkshopListComponent } from './workshop-list/workshop-list.component';
8+
import { WorkshopCreateComponent } from './workshop-create/workshop-create.component';
79

810

911
const routes: Routes = [
1012
HomeComponent.Route,
1113
ProfileEditorComponent.Route,
14+
WorkshopListComponent.Route,
15+
WorkshopCreateComponent.Route,
1216
GateComponent.Route,
1317
{ path: 'admin', title: 'Admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },
1418
];

frontend/src/app/app.module.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import { NavigationComponent } from './navigation/navigation.component';
3232
import { ErrorDialogComponent } from './navigation/error-dialog/error-dialog.component';
3333
import { HomeComponent } from './home/home.component';
3434
import { GateComponent } from './gate/gate.component';
35-
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
35+
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
36+
import { WorkshopListComponent } from './workshop-list/workshop-list.component';
37+
import { WorkshopCreateComponent } from './workshop-create/workshop-create.component';
3638

3739
@NgModule({
3840
declarations: [
@@ -41,7 +43,9 @@ import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.
4143
ErrorDialogComponent,
4244
HomeComponent,
4345
GateComponent,
44-
ProfileEditorComponent
46+
ProfileEditorComponent,
47+
WorkshopListComponent,
48+
WorkshopCreateComponent
4549
],
4650
imports: [
4751
BrowserModule,

frontend/src/app/navigation/navigation.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
<a mat-list-item *ngIf="adminPermission$ | async" routerLink="/admin">Admin</a>
1414
<mat-divider></mat-divider>
1515
<a mat-list-item routerLink="/profile">Profile</a>
16+
<a mat-list-item routerLink="/workshop-list">View Workshops</a>
17+
<a mat-list-item routerLink="/workshop-create">Create Workshops</a>
1618
<a mat-list-item (click)="auth.signOut()">Sign out</a>
1719
</ng-template>
1820
</mat-nav-list>

frontend/src/app/workshop-create/workshop-create.component.css

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>workshop-create works!</p>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { WorkshopCreateComponent } from './workshop-create.component';
4+
5+
describe('WorkshopCreateComponent', () => {
6+
let component: WorkshopCreateComponent;
7+
let fixture: ComponentFixture<WorkshopCreateComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ WorkshopCreateComponent ]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(WorkshopCreateComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Component } from '@angular/core';
2+
import { isAuthenticated } from 'src/app/gate/gate.guard';
3+
import { ActivatedRoute, Route } from '@angular/router';
4+
5+
6+
@Component({
7+
selector: 'app-workshop-create',
8+
templateUrl: './workshop-create.component.html',
9+
styleUrls: ['./workshop-create.component.css']
10+
})
11+
export class WorkshopCreateComponent {
12+
public static Route: Route = {
13+
path: 'workshop-create',
14+
component: WorkshopCreateComponent,
15+
title: 'Create Workshop',
16+
canActivate: [isAuthenticated],
17+
//resolve: { profile: profileResolver }
18+
};
19+
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { WorkshopListService } from './workshop-list.service';
4+
5+
describe('WorkshopListService', () => {
6+
let service: WorkshopListService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(WorkshopListService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Injectable } from '@angular/core';
2+
import { HttpClient } from '@angular/common/http';
3+
import { Observable } from 'rxjs';
4+
5+
export interface Workshop {
6+
id: number;
7+
title: String;
8+
description: String | null;
9+
location: String | null;
10+
// date: datetime | null;
11+
// host: User | null;
12+
}
13+
14+
@Injectable({
15+
providedIn: 'root'
16+
})
17+
18+
export class WorkshopListService {
19+
20+
21+
constructor(protected http: HttpClient) {
22+
23+
}
24+
25+
getWorkshops(): Observable<Workshop[]>{
26+
/* Returns a obersvable list of workshops currently in the database.
27+
28+
Args:
29+
None.
30+
31+
Returns:
32+
Observable list of workshops.
33+
34+
Raises:
35+
None.
36+
*/
37+
return this.http.get<Workshop[]>("/api/workshop");
38+
}
39+
40+
}

frontend/src/app/workshop-list/workshop-list.component.css

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<mat-card>
2+
<mat-card-header>
3+
<mat-card-title>
4+
<h1>Workshops</h1>
5+
</mat-card-title>
6+
</mat-card-header>
7+
8+
<mat-list>
9+
<mat-card *ngFor="let workshop of workshops$ | async" style="background-color: #555555;">
10+
<mat-card-header>
11+
<mat-card-title>{{workshop.title}}</mat-card-title>
12+
<mat-card-subtitle>{{workshop.location}}</mat-card-subtitle>
13+
</mat-card-header>
14+
15+
<p></p>
16+
17+
<mat-card-content>
18+
{{workshop.description}}
19+
</mat-card-content>
20+
</mat-card>
21+
</mat-list>
22+
</mat-card>

0 commit comments

Comments
 (0)