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
88 changes: 88 additions & 0 deletions alembic/versions/cf94844cecc1_allow_email1_to_be_nullable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Allow email1 to be nullable

Revision ID: cf94844cecc1
Revises: 6450d7ab8865
Create Date: 2026-04-20 11:48:33.276491

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'cf94844cecc1'
down_revision: Union[str, Sequence[str], None] = '6450d7ab8865'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_form', sa.Column('email', sa.String(length=255), nullable=True))
op.alter_column('users', 'email1',
existing_type=sa.VARCHAR(length=255),
nullable=True)

# ### commands generated by human ###

# This view exists, drop and create anew
op.execute("DROP VIEW IF EXISTS user_applications")

# Create view with email field
op.execute("""
CREATE VIEW user_applications AS
SELECT f.id,
f.form_type,
f.status,
f.created_by,
f.created_at,
f.updated_by,
f.updated_at,
uf.email,
uf.pi_id,
uf.pi_name,
uf.pi_email,
uf.position,
uf.content
FROM forms f
JOIN user_form uf ON f.id = uf.id
WHERE f.form_type = 'USER'
""")
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands generated by human ###
op.execute("DROP VIEW IF EXISTS user_applications")
op.execute("""
CREATE VIEW user_applications AS
SELECT f.id,
f.form_type,
f.status,
f.created_by,
f.created_at,
f.updated_by,
f.updated_at,
uf.pi_id,
uf.pi_name,
uf.pi_email,
uf.position,
uf.content
FROM forms f
JOIN user_form uf ON f.id = uf.id
WHERE f.form_type = 'USER'
""")
# ### end Alembic commands ###

# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('users', 'email1',
existing_type=sa.VARCHAR(length=255),
nullable=False)
op.drop_column('user_form', 'email')
# ### end Alembic commands ###


23 changes: 16 additions & 7 deletions userapp/api/routes/forms/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from userapp.core.schemas.user_project import UserProjectTableSchema
from userapp.api.routes._util import _patch_user_submit_nodes

CHTC_TICKETING_EMAIL = "chtc@cs.wisc.edu"

ON_USER_FORM_SUBMIT_EMAIL_TEMPLATE = """
Dear {name},

Expand Down Expand Up @@ -48,20 +50,21 @@
The CHTC Research Computing Facilitation team
""".strip()

async def on_user_form_submit(session: AsyncSession, form_id: int, form: None) -> None:
user_form = await session.scalar(
select(UserFormTable)
.where(UserFormTable.id == form_id)
)
async def on_user_form_submit(session: AsyncSession, form_id: int, user_form: UserFormTable) -> None:

# Get an email for the application submission
user = user_form.base_form.created_by_user
email = user.email1 or user_form.email
if email is None:
raise HTTPException(status_code=400, detail="User form must have an email address if user account does not.")
Comment thread
CannonLock marked this conversation as resolved.

# Try sending the user an email
try:
text = format_escaped_template(
ON_USER_FORM_SUBMIT_EMAIL_TEMPLATE,
name=user.name or "user",
)
send_email("chtc@cs.wisc.edu", user.email1, "CHTC Account Application Received", text)
send_email(CHTC_TICKETING_EMAIL, email, CHTC_TICKETING_EMAIL, "CHTC Account Application Received", text)
except Exception:
# we don't care if the email send fails
pass
Expand All @@ -83,6 +86,12 @@ async def on_user_form_accept(session: AsyncSession, form_id: int, form: UserFor

user.active = True

# Set the users email if not already set
if user.email1 is None:
if form.email is None:
raise HTTPException(status_code=400, detail="User has no email address and no email provided in form patch")
user.email1 = form.email
Comment thread
CannonLock marked this conversation as resolved.

# If we are not preserving the users data dump all of their groups
if not form.preserve_existing_data:

Expand Down Expand Up @@ -117,7 +126,7 @@ async def on_user_form_accept(session: AsyncSession, form_id: int, form: UserFor
ON_USER_FORM_APPROVAL_EMAIL_TEMPLATE,
name=user.name or "user",
)
send_email("chtc@cs.wisc.edu", user.email1, "CHTC Account Application Approved", text)
send_email(CHTC_TICKETING_EMAIL, user.email1, CHTC_TICKETING_EMAIL, "CHTC Account Application Approved", text)
except Exception:
# we don't care if the email send fails
pass
Expand Down
5 changes: 3 additions & 2 deletions userapp/api/routes/forms/user_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,22 @@ async def create_user_form(
}
user_form_schema = UserFormTableSchema(
id=created_base_form.id,
email=form.email,
pi_id=form.pi_id,
pi_name=form.pi_name,
pi_email=form.pi_email,
position=form.position,
content=form_content
)
await create_one_endpoint(session, UserFormTable, user_form_schema)
user_form = await create_one_endpoint(session, UserFormTable, user_form_schema)

# Flush session so we can get all the fields when we send the objects back as a view
session.flush()

# Trigger the None->Pending transition
trigger = form_triggers.get((FormTypeEnum.USER, None, FormStatusEnum.PENDING))
if trigger:
await trigger(session, created_base_form.id, None)
await trigger(session, created_base_form.id, user_form)
Comment thread
CannonLock marked this conversation as resolved.

user_application_form = await get_one_endpoint(session, UserApplicationViewTable, created_base_form.id)
return user_application_form
Expand Down
3 changes: 1 addition & 2 deletions userapp/api/routes/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,12 +458,11 @@ async def oidc_callback(request: Request, response: Response, session=Depends(se
)
user_info_resp.raise_for_status()
user_info = user_info_resp.json()
print(user_info)

user = UserTable(
# name is required so fallback to netid if no name is found
name=user_info.get("name") or user_info.get("sub"),
email1=user_info["email"],
email1=user_info.get("email", None),
netid=user_info.get("sub"),
active=False,
is_admin=False,
Expand Down
Loading
Loading