Skip to content

Commit

Permalink
2 sol posts (#26)
Browse files Browse the repository at this point in the history
* Created post data model (#12)

Add Post datamodel

* Add post entity (#13)

Add post entity

* Build forum post frontend part 1 (#16)

* build forum post frontend part 1

* created viewforum as a page to look at all forums

* Start working on forwarding form responses to the viewforum component.

* Clean up whitespace on makeforum.

* Edited onSuccess to include dynamic responses as input comes in. Created VewForm HTML similar to STATS page.

---------

Co-authored-by: angelinasu57 <[email protected]>

* Finish creating viewForum component HTML to show form responses. (#17)

* Finish creating viewForum component HTML with Response, First and Last name, and date for each form response.

* Add in Last Name in the table to keep track of the responses, as per suggestion.

* Adjusted endpoints and remove comments.

* Implement get post and create post services and api routes (#18)


Implement create and getall API routes top down to retrieve and create posts
- Created post service file
- Implement create and get All method in post service
- Implement api routes and Sqlchemy table for PostEntity
- Implement New findUsers method in UserService
- Implement fully functional post service

* Integrate frontend and backend

Fully integrate frontend with backend enabling viewing list of posts and creating posts via API endpoints

* Build unit tests that cover post service (#22)

* Add finishing touches to Views as well as error handling 

Add styling to HTML elements and navigation between forum pages.
Implement error handling for post creation

---------

Co-authored-by: Warren Quadland <[email protected]>
Co-authored-by: Aayush Mehra <[email protected]>
Co-authored-by: angelinasu57 <[email protected]>
Co-authored-by: Angelina Su <[email protected]>
  • Loading branch information
5 people authored Mar 30, 2023
1 parent c2a2927 commit b985445
Show file tree
Hide file tree
Showing 31 changed files with 547 additions and 24 deletions.
19 changes: 19 additions & 0 deletions backend/api/post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from fastapi import APIRouter, Depends, HTTPException
from ..services import PostService, UserService
from ..models import Post
from .authentication import registered_user

api = APIRouter(prefix="/api/post")


@api.post("", response_model=Post, tags=['Post'])
def create(post: Post, post_svc: PostService = Depends(), usr_svc: UserService = Depends()):
try:
user_entity = usr_svc.findUser(post.user)
except:
raise HTTPException(status_code=422, detail=str("User Not Registered"))
return post_svc.create(post,user_entity)

@api.get("", response_model=list[Post], tags=['Post'])
def getAll(post_svc: PostService = Depends()):
return post_svc.getAll()
1 change: 1 addition & 0 deletions backend/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .role_entity import RoleEntity
from .permission_entity import PermissionEntity
from .user_role_entity import user_role_table
from .post_entity import PostEntity


__authors__ = ["Kris Jordan"]
Expand Down
60 changes: 60 additions & 0 deletions backend/entities/post_entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'''User accounts for all registered users in the application.'''


from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import Self
from .entity_base import EntityBase
#from ..models import User
from ..models import Post
from fastapi import Depends
from .user_entity import UserEntity
from .post_votes_entity import post_votes_table
#from ..services import UserPostService

__authors__ = ['Kris Jordan']
__copyright__ = 'Copyright 2023'
__license__ = 'MIT'


class PostEntity(EntityBase):
__tablename__ = 'posts'

id: Mapped[int] = mapped_column(Integer, primary_key=True)
content: Mapped[str] = mapped_column(String(64), nullable=False, default='')

user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
user: Mapped[UserEntity] = relationship("UserEntity",back_populates='posts')

votes: Mapped[list[UserEntity]] = relationship(secondary=post_votes_table)

timestamp: Mapped[str] = mapped_column(String(64), nullable=False, default='')

@classmethod
def from_model(cls, model: Post, user: UserEntity ) -> Self:
#user_svc: UserPostService = Depends()
return cls(
id=model.id,
content=model.content,
user = user,
votes= [],
timestamp=model.timestamp,

#user_svc.findUser(model.user)
#[user_svc.findUser(vote) for vote in model.votes]
)

def to_model(self) -> Post:
vote_num = [vote.to_model() for vote in self.votes]
return Post(
id=self.id,
content=self.content,
user=self.user.to_model(),
votes=vote_num,
timestamp=self.timestamp,
)

def update(self, model: Post) -> None:
self.content = model.content


9 changes: 9 additions & 0 deletions backend/entities/post_votes_entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from sqlalchemy import Table, Column, ForeignKey
from .entity_base import EntityBase

post_votes_table = Table(
"post_votes",
EntityBase.metadata,
Column('user_id', ForeignKey('user.id'), primary_key=True),
Column('post_id', ForeignKey('posts.id'), primary_key=True)
)
2 changes: 1 addition & 1 deletion backend/entities/role_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class RoleEntity(EntityBase):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String, unique=True)

users: Mapped[list['UserEntity']] = relationship(secondary=user_role_table, back_populates='roles')
users: Mapped[list['UserEntity']] = relationship(secondary=user_role_table)
permissions: Mapped[list['PermissionEntity']] = relationship(back_populates='role')

@classmethod
Expand Down
9 changes: 8 additions & 1 deletion backend/entities/user_entity.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'''User accounts for all registered users in the application.'''


from sqlalchemy import Integer, String
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import Self
from .entity_base import EntityBase
from .user_role_entity import user_role_table
from ..models import User
from .post_votes_entity import post_votes_table


__authors__ = ['Kris Jordan']
Expand All @@ -32,6 +33,12 @@ class UserEntity(EntityBase):

roles: Mapped[list['RoleEntity']] = relationship(secondary=user_role_table, back_populates='users')
permissions: Mapped['PermissionEntity'] = relationship(back_populates='user')

#create Secondary table for posts???
posts: Mapped[list['PostEntity']] = relationship(back_populates='user')

votes: Mapped[list['PostEntity']] = relationship(secondary=post_votes_table, back_populates='votes')


@classmethod
def from_model(cls, model: User) -> Self:
Expand Down
3 changes: 2 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Entrypoint of backend API exposing the FastAPI `app` to be served by an application server such as uvicorn."""

from fastapi import FastAPI
from .api import health, static_files, profile, authentication, user
from .api import health, static_files, profile, authentication, user, post
from .api.admin import users as admin_users
from .api.admin import roles as admin_roles

Expand All @@ -26,4 +26,5 @@
app.include_router(authentication.api)
app.include_router(admin_users.api)
app.include_router(admin_roles.api)
app.include_router(post.api)
app.mount("/", static_files.StaticFileMiddleware(directory="./static"))
6 changes: 6 additions & 0 deletions backend/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from .user import User, ProfileForm, NewUser
from .role import Role
from .role_details import RoleDetails
from .post import Post
# import sys
# sys.path.append("/workspace/backend/models")
# import



__authors__ = ["Kris Jordan"]
__copyright__ = "Copyright 2023"
Expand Down
15 changes: 15 additions & 0 deletions backend/models/post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Post model serves as data model for posts in the Forum"""

from pydantic import BaseModel
from . import User

class Post(BaseModel):
id: int | None = None
content: str
user: User
votes: list[User] = []
timestamp: str




4 changes: 3 additions & 1 deletion backend/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .user import UserService
from .permission import PermissionService, UserPermissionError
from .role import RoleService
from .role import RoleService
from .post import PostService
from .user_post import UserPostService
26 changes: 26 additions & 0 deletions backend/services/post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi import Depends
from sqlalchemy import select, or_, func
from sqlalchemy.orm import Session
from ..database import db_session
from ..models import User, Role, RoleDetails, Permission, Post
from ..entities import RoleEntity, PermissionEntity, UserEntity
from ..entities.post_entity import PostEntity
from .permission import PermissionService, UserPermissionError


class PostService:

def __init__(self, session: Session = Depends(db_session), permission: PermissionService = Depends()):
self._session = session
self._permission = permission

def create(self, post: Post, user: UserEntity) -> Post:
post_entity = PostEntity.from_model(post,user)
self._session.add(post_entity)
self._session.commit()
return post_entity.to_model()

def getAll(self) -> list[Post]:
query = select(PostEntity)
entities = self._session.scalars(query).all()
return [entity.to_model() for entity in entities]
9 changes: 9 additions & 0 deletions backend/services/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def search(self, _subject: User, query: str) -> list[User]:
statement = statement.where(criteria).limit(10)
entities = self._session.execute(statement).scalars()
return [entity.to_model() for entity in entities]

def findUser(self, _subject: User) -> UserEntity:
# query = select(UserEntity).filter_by(id=_subject.id)
query = select(UserEntity).where(UserEntity.pid == _subject.pid)
user_entity: UserEntity = self._session.execute(query).scalar()
return user_entity



def list(self, subject: User, pagination_params: PaginationParams) -> Paginated[User]:
"""List Users.
Expand Down Expand Up @@ -148,3 +156,4 @@ def update(self, subject: User, user: User) -> User:
entity.update(user)
self._session.commit()
return entity.to_model()

22 changes: 22 additions & 0 deletions backend/services/user_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from fastapi import Depends
from sqlalchemy import select, or_, func
from sqlalchemy.orm import Session
from ..database import db_session
from ..models import User, Paginated, PaginationParams
from ..entities import UserEntity
from .permission import PermissionService
from . import *

class UserPostService:

_session: Session
_permission: PermissionService

def findUser(self, _subject: User, user_svc: 'UserService' = Depends()) -> UserEntity:
return user_svc.findUser(_subject)






50 changes: 50 additions & 0 deletions backend/test/services/post_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import pytest

from sqlalchemy.orm import Session
from ...models import User, Post, Role
from ...entities import UserEntity, RoleEntity
from ...services import PostService, UserService
from sqlalchemy.exc import IntegrityError

# Mock Models
user = User(id=1, pid=111111111, onyen='onyen',first_name="first", last_name = "last", email='[email protected]', pronouns="He/Him/His", permissions = [])

post_0 = Post(id = 1, content = "post", user = user, votes = [], timestamp = "date0")
post_1 = Post(id = 2, content = "content", user = user, votes = [], timestamp = "date1")

@pytest.fixture(autouse=True)
def setup_teardown(test_session: Session):
# Bootstrap root User without any perms/roles
user_entity = UserEntity.from_model(user)
test_session.add(user_entity)

test_session.commit()
yield

@pytest.fixture()
def postservice(test_session: Session):
return PostService(test_session)

@pytest.fixture()
def userservice(test_session: Session):
return UserService(test_session)

def test_create_single_post_pass(postservice: PostService, userservice: UserService):
user_entity: UserEntity = userservice.findUser(user)
post_entity: Post = postservice.create(post_0, user_entity)
assert post_entity == post_0

def test_create_two_posts_pass(postservice: PostService, userservice: UserService):
user_entity: UserEntity = userservice.findUser(user)
post_entity_0: Post = postservice.create(post_0, user_entity)
post_entity_1: Post = postservice.create(post_1, user_entity)
assert post_entity_0 == post_0
assert post_entity_1 == post_1

def test_getall_posts_pass(postservice: PostService, userservice: UserService):
user_entity: UserEntity = userservice.findUser(user)
post_entity_0: Post = postservice.create(post_0, user_entity)
post_entity_1: Post = postservice.create(post_1, user_entity)
posts = postservice.getAll()
assert posts[0] == post_0
assert posts[1] == post_1
4 changes: 3 additions & 1 deletion frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { AppTitleStrategy } from './app-title.strategy';
import { GateComponent } from './gate/gate.component';
import { HomeComponent } from './home/home.component';
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
import { ForumComponent } from './forum/forum.component';
import { viewforumComponent } from './viewforum/viewforum.component';
import { ForumComponent } from './makeforum/makeforum.component';


const routes: Routes = [
HomeComponent.Route,
ProfileEditorComponent.Route,
GateComponent.Route,
viewforumComponent.Route,
ForumComponent.Route,
{ path: 'admin', title: 'Admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },
];
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import { NavigationComponent } from './navigation/navigation.component';
import { ErrorDialogComponent } from './navigation/error-dialog/error-dialog.component';
import { HomeComponent } from './home/home.component';
import { GateComponent } from './gate/gate.component';
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
import { ForumComponent } from './forum/forum.component';
import { ProfileEditorComponent } from './profile/profile-editor/profile-editor.component';
import { ForumComponent } from './makeforum/makeforum.component';
import { viewforumComponent } from './viewforum/viewforum.component';

@NgModule({
declarations: [
Expand All @@ -43,7 +44,8 @@ import { ForumComponent } from './forum/forum.component';
HomeComponent,
GateComponent,
ProfileEditorComponent,
ForumComponent
ForumComponent,
viewforumComponent
],
imports: [
BrowserModule,
Expand Down
Empty file.
1 change: 0 additions & 1 deletion frontend/src/app/forum/forum.component.html

This file was deleted.

13 changes: 0 additions & 13 deletions frontend/src/app/forum/forum.component.ts

This file was deleted.

13 changes: 13 additions & 0 deletions frontend/src/app/makeforum/makeforum.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.button {
transition-duration: 0.4s;
}

.button:hover {
background-color: #4CAF50; /* Green */
color: white;
}


a {
text-decoration: underline;
}
14 changes: 14 additions & 0 deletions frontend/src/app/makeforum/makeforum.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h1>Forum</h1>

<h3>Make a Forum Post With Resources</h3>

<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div>
<input id="content" type="text" formControlName="content">
</div>
<button class="button" type="submit">Submit</button>
</form>

<a mat-list-item routerLink="/viewforum">Back</a>


Loading

0 comments on commit b985445

Please sign in to comment.