Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions alembic/versions/f2ec55925c4c_external_management_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
"""External management update

Revision ID: f2ec55925c4c
Revises: 47f8adc9859b
Create Date: 2026-05-15 12:54:31.384728

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


# revision identifiers, used by Alembic.
revision: str = 'f2ec55925c4c'
down_revision: Union[str, Sequence[str], None] = '47f8adc9859b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

entity_manager_enum = postgresql.ENUM('APPLICATION', 'MANIFEST', 'MORGRIDGE_AD', name='entity_manager_enum', create_type=False)


def upgrade() -> None:
"""Upgrade schema."""
bind = op.get_bind()
entity_manager_enum.create(bind, checkfirst=True)

# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_groups', sa.Column('managed_by', entity_manager_enum, nullable=False, server_default='APPLICATION'))
op.add_column('user_groups', sa.Column('created_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
op.add_column('user_groups', sa.Column('updated_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
op.create_index('idx_user_group_managed_by', 'user_groups', ['group_id', 'managed_by', 'user_id'], unique=False)
op.add_column('user_projects', sa.Column('managed_by', entity_manager_enum, nullable=False, server_default='APPLICATION'))
op.add_column('user_projects', sa.Column('created_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
op.add_column('user_projects', sa.Column('updated_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
op.create_index('idx_user_project_managed_by', 'user_projects', ['project_id', 'managed_by', 'user_id'], unique=False)
op.add_column('users', sa.Column('created_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
op.add_column('users', sa.Column('updated_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False))
# ### end Alembic commands ###

# ### commands generated by human ###
op.execute('DROP VIEW IF EXISTS joined_projects')
op.execute("""
CREATE VIEW joined_projects AS
SELECT
u.id,
p.id AS project_id,
p.name AS project_name,
p.staff1 AS project_staff1,
p.staff2 AS project_staff2,
p.status AS project_status,
p.last_contact AS project_last_contact,
p.accounting_group AS project_accounting_group,
up.is_primary AS is_primary,
up.managed_by,
up.created_at,
up.updated_at,
u.name,
u.username,
u.email1,
u.email2,
u.netid,
u.netid_exp_datetime,
u.phone1,
u.phone2,
u.is_admin,
u.active,
u.date,
u.unix_uid,
u.position,
up.role,
(
SELECT n.ticket
FROM notes n
LEFT JOIN user_notes un ON n.id = un.note_id
WHERE un.user_id = up.user_id AND un.project_id = up.project_id
ORDER BY n.id DESC
LIMIT 1
) AS last_note_ticket
FROM user_projects up
JOIN users u ON up.user_id = u.id
JOIN projects p ON up.project_id = p.id
""")
op.execute("""
CREATE OR REPLACE VIEW group_users AS
SELECT
ug.group_id,
ug.user_id,
ug.managed_by,
ug.created_at,
ug.updated_at,
u.name,
u.netid,
u.username,
u.is_admin,
u.active,
u.unix_uid
FROM user_groups ug
JOIN users u ON ug.user_id = u.id
""")
op.execute("""
CREATE VIEW user_group_memberships AS
SELECT
ug.group_id,
ug.user_id,
ug.managed_by,
ug.created_at,
ug.updated_at,
g.name,
g.point_of_contact,
g.unix_gid,
g.has_groupdir
FROM user_groups ug
JOIN groups g ON ug.group_id = g.id
""")
# Update the created_at date to the previous date field value
op.execute("UPDATE users SET created_at = date WHERE date IS NOT NULL")
# ### end commands generated by human ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands generated by human ###
op.execute("DROP VIEW IF EXISTS user_group_memberships")
op.execute("DROP VIEW IF EXISTS group_users")
op.execute("DROP VIEW IF EXISTS joined_projects")
# ### end commands generated by human ###

# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'updated_at')
op.drop_column('users', 'created_at')
op.drop_index('idx_user_project_managed_by', table_name='user_projects')
op.drop_column('user_projects', 'updated_at')
op.drop_column('user_projects', 'created_at')
op.drop_column('user_projects', 'managed_by')
op.drop_index('idx_user_group_managed_by', table_name='user_groups')
op.drop_column('user_groups', 'updated_at')
op.drop_column('user_groups', 'created_at')
op.drop_column('user_groups', 'managed_by')
# ### end Alembic commands ###

# ### commands generated by human ###
op.execute("""
CREATE VIEW joined_projects AS
SELECT
u.id,
p.id AS project_id,
p.name AS project_name,
p.staff1 AS project_staff1,
p.staff2 AS project_staff2,
p.status AS project_status,
p.last_contact AS project_last_contact,
p.accounting_group AS project_accounting_group,
up.is_primary AS is_primary,
u.name,
u.username,
u.email1,
u.email2,
u.netid,
u.netid_exp_datetime,
u.phone1,
u.phone2,
u.is_admin,
u.active,
u.date,
u.unix_uid,
u.position,
up.role,
(
SELECT n.ticket
FROM notes n
LEFT JOIN user_notes un ON n.id = un.note_id
WHERE un.user_id = up.user_id AND un.project_id = up.project_id
ORDER BY n.id DESC
LIMIT 1
) AS last_note_ticket
FROM user_projects up
JOIN users u ON up.user_id = u.id
JOIN projects p ON up.project_id = p.id
""")
# ### end commands generated by human ###

bind = op.get_bind()
entity_manager_enum.drop(bind, checkfirst=True)

6 changes: 3 additions & 3 deletions userapp/api/load_options.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from sqlalchemy.orm import selectinload

from userapp.core.models.tables import User as UserTable, Note as NoteTable, Group as GroupTable
from userapp.core.models.views import JoinedProjectView as JoinedProjectViewTable
from userapp.core.models.tables import User as UserTable, Note as NoteTable
from userapp.core.models.views import JoinedProjectView as JoinedProjectViewTable, UserGroupView as UserGroupViewTable

user_load_options = [
selectinload(UserTable.notes).joinedload(NoteTable.author),
selectinload(UserTable.groups).joinedload(GroupTable.point_of_contact_user),
selectinload(UserTable.groups).selectinload(UserGroupViewTable.point_of_contact_user),
selectinload(UserTable.projects).selectinload(JoinedProjectViewTable.staff1_user),
selectinload(UserTable.projects).selectinload(JoinedProjectViewTable.staff2_user),
selectinload(UserTable.user_forms),
Expand Down
2 changes: 2 additions & 0 deletions userapp/api/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from userapp.api.routes.forms import router as forms_router
from .groups import router as groups_router
from .managed import router as managed_router
from .pi_projects import router as pi_projects_router
from .projects import router as projects_router
from .security import router as security_router
Expand All @@ -12,6 +13,7 @@
routes_router,
forms_router,
groups_router,
managed_router,
pi_projects_router,
projects_router,
security_router,
Expand Down
14 changes: 8 additions & 6 deletions userapp/api/routes/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

from sqlalchemy import select, delete

from userapp.core.models.views import GroupUserView
from userapp.core.schemas.user_group import UserGroupPost
from userapp.db import session_generator
from userapp.query_parser import get_filter_query_params
from userapp.api.routes.security import check_is_admin
from userapp.api.util import list_endpoint, get_one_endpoint, create_one_endpoint, update_one_endpoint, list_select_stmt, \
delete_one_endpoint, with_db_error_handling
from userapp.core.schemas.general import Relationship
from userapp.core.schemas.general import Relationship, GroupUserView as GroupUserViewSchema
from userapp.core.schemas.groups import GroupGet, GroupPost, GroupPatch
from userapp.core.schemas.users import UserGet

Expand Down Expand Up @@ -54,18 +56,18 @@ async def update_group(group_id: int, group: GroupPatch, session=Depends(session


@router.get("/{group_id}/users")
async def get_group_users(group_id: int, response: Response, page: int = 0, page_size: int = 100, filter_query_params=Depends(get_filter_query_params), session=Depends(session_generator)) -> List[UserGet]:
async def get_group_users(group_id: int, response: Response, page: int = 0, page_size: int = 100, filter_query_params=Depends(get_filter_query_params), session=Depends(session_generator)) -> List[GroupUserViewSchema]:
"""Get users associated with a group"""

select_stmt = select(UserTable).join(UserGroup).where(UserGroup.group_id == group_id)
return await list_select_stmt(session, select_stmt, UserTable, response, filter_query_params, page, page_size)
select_stmt = select(GroupUserView).where(GroupUserView.group_id == group_id)
return await list_select_stmt(session, select_stmt, GroupUserView, response, filter_query_params, page, page_size)

@with_db_error_handling
@router.post("/{group_id}/users", status_code=201)
async def add_user_to_group(group_id: int, user: Relationship, session=Depends(session_generator)) -> dict:
async def add_user_to_group(group_id: int, user: UserGroupPost, session=Depends(session_generator)) -> dict:
"""Add user to a group"""

user_group = UserGroup(user_id=user.id, group_id=group_id)
user_group = UserGroup(group_id=group_id, **user.model_dump(exclude_unset=True))
session.add(user_group)
Comment thread
CannonLock marked this conversation as resolved.
return {"message": "Users added to group successfully"}

Expand Down
9 changes: 9 additions & 0 deletions userapp/api/routes/managed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi import APIRouter

from .manifest import router as manifest_router
from .morgridge_ad import router as morgridge_ad_router

router = APIRouter(prefix="/managed")
router.include_router(manifest_router)
router.include_router(morgridge_ad_router)

Loading
Loading