merge dev (#16)
Build and Push Lunchtime Images (Kaniko) / build-and-push (push) Successful in 2m1s

- Closes #15
- Other fixes and improvements here and there

Co-authored-by: fhs52267 <sgruber.aitb-m2024@fh-salzburg.ac.at>
Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
2026-04-07 21:27:06 +02:00
parent 5fd5751199
commit 2b42033c70
7 changed files with 105 additions and 29 deletions
+39 -1
View File
@@ -403,10 +403,47 @@ def confirm_account_action(token: str) -> dict[str, Any]:
raise HTTPException(status_code=422, detail="Token is required") raise HTTPException(status_code=422, detail="Token is required")
with get_connection() as conn: with get_connection() as conn:
confirmation = consume_confirmation_token(conn, token.strip()) normalized_token = token.strip()
try:
confirmation = consume_confirmation_token(conn, normalized_token)
already_consumed = False
except HTTPException as exc:
if exc.status_code != 409 or str(exc.detail) != "Confirmation token already used":
raise
confirmation = conn.execute(
"""
SELECT token, user_id, action, process_id, email, new_email, new_user_id, created_at, expires_at, consumed_at
FROM account_confirmation_tokens
WHERE token = ?
""",
(normalized_token,),
).fetchone()
if not confirmation:
raise HTTPException(status_code=404, detail="Confirmation token not found")
already_consumed = True
action = str(confirmation["action"]) action = str(confirmation["action"])
confirmed_user_id = str(confirmation["user_id"]) confirmed_user_id = str(confirmation["user_id"])
if already_consumed:
if action == "user_id_change_confirm":
migrated_user_id = str(confirmation["new_user_id"] or "").strip() or confirmed_user_id
return {
"status": "already_confirmed",
"action": action,
"user_id": migrated_user_id,
}
return {
"status": "already_confirmed",
"action": action,
"user_id": confirmed_user_id,
}
if action == "register_confirm": if action == "register_confirm":
conn.execute( conn.execute(
"UPDATE user_profiles SET email_confirmed = 1, updated_at = ? WHERE user_id = ?", "UPDATE user_profiles SET email_confirmed = 1, updated_at = ? WHERE user_id = ?",
@@ -676,6 +713,7 @@ def get_my_order_access(order_id: str, user_id: str = Depends(get_existing_user_
ensure_order_exists(order_id) ensure_order_exists(order_id)
with get_connection() as conn: with get_connection() as conn:
upsert_user_order_tokens(conn, clean_id, order_id)
auth = get_user_order_tokens(conn, clean_id, order_id) auth = get_user_order_tokens(conn, clean_id, order_id)
return { return {
+2 -2
View File
@@ -4,7 +4,7 @@ services:
context: backend context: backend
dockerfile: Containerfile dockerfile: Containerfile
volumes: volumes:
- ../.data:/app/data - ./.data:/app/data
- ../config.yaml:/app/config.yaml:ro - ../config.yaml:/app/config.yaml:ro
- ./backend/app:/app/app - ./backend/app:/app/app
environment: environment:
@@ -28,7 +28,7 @@ services:
- ./frontend/vite.config.ts:/app/vite.config.ts - ./frontend/vite.config.ts:/app/vite.config.ts
- ./frontend/tsconfig.json:/app/tsconfig.json - ./frontend/tsconfig.json:/app/tsconfig.json
- ./frontend/tsconfig.node.json:/app/tsconfig.node.json - ./frontend/tsconfig.node.json:/app/tsconfig.node.json
- /app/node_modules - /app/node_modules # Reset node_modules by running with '--renew-anon-volumes'
command: npm run dev -- --host 0.0.0.0 --port 8000 command: npm run dev -- --host 0.0.0.0 --port 8000
mailpit: mailpit:
+17
View File
@@ -9,6 +9,8 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.1.0", "@ant-design/icons": "^6.1.0",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"antd": "^5.27.3", "antd": "^5.27.3",
"dompurify": "^3.3.3", "dompurify": "^3.3.3",
"marked": "^16.0.0", "marked": "^16.0.0",
@@ -930,6 +932,21 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@mdi/js": {
"version": "7.4.47",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
"license": "Apache-2.0"
},
"node_modules/@mdi/react": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz",
"integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.7.2"
}
},
"node_modules/@rc-component/async-validator": { "node_modules/@rc-component/async-validator": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
+2
View File
@@ -10,6 +10,8 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.1.0", "@ant-design/icons": "^6.1.0",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"antd": "^5.27.3", "antd": "^5.27.3",
"dompurify": "^3.3.3", "dompurify": "^3.3.3",
"marked": "^16.0.0", "marked": "^16.0.0",
+2
View File
@@ -416,6 +416,7 @@ export default function App() {
message.success("Registration email sent. Open the link in your inbox to finish setup."); message.success("Registration email sent. Open the link in your inbox to finish setup.");
} catch (error: any) { } catch (error: any) {
message.error(error?.message || "Could not create account."); message.error(error?.message || "Could not create account.");
throw error;
} }
}; };
@@ -430,6 +431,7 @@ export default function App() {
message.success("Migration email sent. Open the link in your inbox to complete migration."); message.success("Migration email sent. Open the link in your inbox to complete migration.");
} catch (error: any) { } catch (error: any) {
message.error(error?.message || "Could not migrate account."); message.error(error?.message || "Could not migrate account.");
throw error;
} }
}; };
@@ -6,6 +6,8 @@ import {
formatEstimatedTotal, formatEstimatedTotal,
stripPriceDecorations, stripPriceDecorations,
} from "../../lib/orderFormatting"; } from "../../lib/orderFormatting";
import Icon from "@mdi/react";
import { mdiFood } from "@mdi/js";
const { Text } = Typography; const { Text } = Typography;
@@ -40,6 +42,12 @@ function getEstimatedTotalText(rawValue: unknown): string | null {
return null; return null;
} }
function getOrderIconStyle(isClosed: boolean) {
return {
opacity: isClosed ? 0.25 : 0.6,
};
}
export default function HomeOrdersTable({ export default function HomeOrdersTable({
orders, orders,
selectedState, selectedState,
@@ -81,31 +89,40 @@ export default function HomeOrdersTable({
item.submission?.estimated_total, item.submission?.estimated_total,
); );
const isClosed = !!item.closed;
const closedStr = isClosed ? " Closed" : "Open";
const dateStr =
item.created_at
? new Date(item.created_at).toLocaleString()
: "Unknown creation time";
return ( return (
<Space direction="vertical" size={0}> <Space size={16}>
<Space size={8} align="center"> <Icon path={mdiFood} size={1} style={getOrderIconStyle(isClosed)} />
<Text strong>{item.title || item.id}</Text> <Space direction="vertical" size={0}>
{item.is_owner ? <Tag color="geekblue">Owner</Tag> : null} <Space size={8} align="center">
{state === "paid" ? <Tag color="green">Paid</Tag> : null} <Text strong>{item.title || item.id}</Text>
{state === "unpaid" ? ( {item.is_owner ? <Tag color="geekblue">Owner</Tag> : null}
<Tag color="volcano">Unpaid</Tag> {state === "paid" ? <Tag color="green">Paid</Tag> : null}
) : null} {state === "unpaid" ? (
{state === "pending" ? <Tag color="blue">Pending</Tag> : null} <Tag color="volcano">Unpaid</Tag>
) : null}
{state === "pending" ? <Tag color="blue">Pending</Tag> : null}
</Space>
<Text type="secondary">
{<Tooltip title={`Order is ${closedStr.toLocaleLowerCase()}`}>{closedStr}</Tooltip>}{" • "}
{<Tooltip title={`Created at: ${dateStr}`}>{dateStr}</Tooltip>}
{item.is_participant && formatted && (
<>
{" • "}
<Tooltip title="Your order">
{formatted}
</Tooltip>
</>
)}
</Text>
</Space> </Space>
<Text type="secondary">
{item.created_at
? new Date(item.created_at).toLocaleString()
: "Unknown creation time"}
{item.is_participant && formatted && `${formatted}`}
{item.is_participant && estimateText && (
<>
{" • "}
<Tooltip title="Rough estimate based on menu prices. Final amount may differ.">
{estimateText}
</Tooltip>
</>
)}
</Text>
</Space> </Space>
); );
}, },
@@ -124,7 +141,7 @@ export default function HomeOrdersTable({
navigateTo(`/order/${item.id}/admin`); navigateTo(`/order/${item.id}/admin`);
}} }}
> >
Edit Manage
</Button> </Button>
) : null, ) : null,
}, },
+2 -2
View File
@@ -428,7 +428,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
navigateTo(`/order/${orderId}/admin`); navigateTo(`/order/${orderId}/admin`);
}} }}
> >
Edit order Manage order
</Link> </Link>
</Text> </Text>
} }
@@ -454,7 +454,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
type="primary" type="primary"
onClick={() => setIsEditingSubmittedOrder(true)} onClick={() => setIsEditingSubmittedOrder(true)}
> >
Edit Order Manage order
</Button> </Button>
) : ( ) : (
<Button <Button