Skip to content

Commit

Permalink
Merge pull request #181 from SFTtech/milo/release-automation
Browse files Browse the repository at this point in the history
misc stuff in preparation for next release
  • Loading branch information
mikonse committed Jan 2, 2024
2 parents 48541eb + 257d1ec commit cef6a5a
Show file tree
Hide file tree
Showing 44 changed files with 573 additions and 318 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/push_on_master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ jobs:
environment: demo
if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/heads/master')}}
needs:
- build_and_test_frontend
- build_and_test_backend
- build-debs
runs-on: ubuntu-latest
steps:
Expand All @@ -155,6 +157,8 @@ jobs:
name: "Attach assets to release"
if: ${{ !failure() && !cancelled() && startsWith(github.ref, 'refs/tags/') }}
needs:
- build_and_test_frontend
- build_and_test_backend
- build-debs
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion abrechnung/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Abrechnung - feature complete payment management and bookkeeping."""

__version__ = "0.10.1"
__version__ = "0.12.0"

MAJOR_VERSION = __version__.split(".")[0]
MINOR_VERSION = __version__.split(".")[1]
Expand Down
30 changes: 15 additions & 15 deletions abrechnung/http/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,17 @@ def __init__(self, config: Config):
allow_headers=["*"],
)

self.api.add_exception_handler(NotFoundError, self._not_found_exception_handler)
self.api.add_exception_handler(PermissionError, self._permission_error_exception_handler)
self.api.add_exception_handler(asyncpg.DataError, self._bad_request_exception_handler)
self.api.add_exception_handler(asyncpg.RaiseError, self._bad_request_exception_handler)
bad_request_handler = self.make_generic_exception_handler(status.HTTP_400_BAD_REQUEST)

self.api.add_exception_handler(NotFoundError, self.make_generic_exception_handler(status.HTTP_404_NOT_FOUND))
self.api.add_exception_handler(PermissionError, self.make_generic_exception_handler(status.HTTP_403_FORBIDDEN))
self.api.add_exception_handler(asyncpg.DataError, bad_request_handler)
self.api.add_exception_handler(asyncpg.RaiseError, bad_request_handler)
self.api.add_exception_handler(
asyncpg.IntegrityConstraintViolationError,
self._bad_request_exception_handler,
bad_request_handler,
)
self.api.add_exception_handler(InvalidCommand, self._bad_request_exception_handler)
self.api.add_exception_handler(InvalidCommand, bad_request_handler)
self.api.add_exception_handler(StarletteHTTPException, self._http_exception_handler)

self.uvicorn_config = uvicorn.Config(
Expand All @@ -77,17 +79,15 @@ def _format_error_message(code: int, message: str):
},
)

async def _not_found_exception_handler(self, request: Request, exc: NotFoundError):
return self._format_error_message(status.HTTP_404_NOT_FOUND, str(exc))

async def _permission_error_exception_handler(self, request: Request, exc: PermissionError):
return self._format_error_message(status.HTTP_403_FORBIDDEN, str(exc))
def make_generic_exception_handler(self, status_code: int):
async def handler(request: Request, exc: Exception):
return self._format_error_message(status_code, str(exc))

async def _bad_request_exception_handler(self, request: Request, exc):
return self._format_error_message(status.HTTP_400_BAD_REQUEST, str(exc))
return handler

async def _http_exception_handler(self, request: Request, exc: StarletteHTTPException):
return self._format_error_message(exc.status_code, exc.detail)
async def _http_exception_handler(self, request: Request, exc: Exception):
if isinstance(exc, StarletteHTTPException):
return self._format_error_message(exc.status_code, exc.detail)

async def _setup(self):
self.db_pool = await create_db_pool(self.cfg.database)
Expand Down
15 changes: 15 additions & 0 deletions frontend/apps/mobile/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { resources, defaultNS } from "@abrechnung/translations";

i18n.use(initReactI18next).init({
ns: [defaultNS],
resources,
defaultNS,
lng: "en",
fallbackLng: "en",
debug: true,
interpolation: { escapeValue: false },
});

export default i18n;
9 changes: 9 additions & 0 deletions frontend/apps/mobile/src/i18next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "i18next";
import type { resources, defaultNS } from "@abrechnung/translations";

declare module "i18next" {
interface CustomTypeOptions {
defaultNS: typeof defaultNS;
resources: (typeof resources)["en"];
}
}
4 changes: 2 additions & 2 deletions frontend/apps/mobile/src/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getAbrechnungReducer } from "@abrechnung/redux";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { configureStore } from "@reduxjs/toolkit";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { persistStore } from "redux-persist";
import { settingsReducer } from "./settingsSlice";
import { uiReducer } from "./uiSlice";
import FilesystemStorage from "redux-persist-filesystem-storage";

const rootReducer = getAbrechnungReducer(AsyncStorage, { settings: settingsReducer, ui: uiReducer });
const rootReducer = getAbrechnungReducer(FilesystemStorage, { settings: settingsReducer, ui: uiReducer });

export const store = configureStore({
reducer: rootReducer,
Expand Down
33 changes: 16 additions & 17 deletions frontend/apps/web/src/app/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Register from "../pages/auth/Register";
import Login from "../pages/auth/Login";
import Logout from "../pages/auth/Logout";
import Group from "../pages/groups/Group";
import PageNotFound from "../pages/PageNotFound";
import ChangeEmail from "../pages/profile/ChangeEmail";
import ChangePassword from "../pages/profile/ChangePassword";
import GroupList from "../pages/groups/GroupList";
import SessionList from "../pages/profile/SessionList";
import ConfirmPasswordRecovery from "../pages/auth/ConfirmPasswordRecovery";
import RequestPasswordRecovery from "../pages/auth/RequestPasswordRecovery";
import Settings from "../pages/profile/Settings";
import { ConfirmEmailChange } from "../pages/auth/ConfirmEmailChange";
import { ConfirmPasswordRecovery } from "../pages/auth/ConfirmPasswordRecovery";
import { ConfirmRegistration } from "../pages/auth/ConfirmRegistration";
import { Login } from "../pages/auth/Login";
import { Logout } from "../pages/auth/Logout";
import { Register } from "../pages/auth/Register";
import { RequestPasswordRecovery } from "../pages/auth/RequestPasswordRecovery";
import { Group } from "../pages/groups/Group";
import { GroupInvite } from "../pages/groups/GroupInvite";
import { GroupList } from "../pages/groups/GroupList";
import { PageNotFound } from "../pages/PageNotFound";
import { ChangeEmail } from "../pages/profile/ChangeEmail";
import { ChangePassword } from "../pages/profile/ChangePassword";
import { Profile } from "../pages/profile/Profile";
import { SessionList } from "../pages/profile/SessionList";
import { Settings } from "../pages/profile/Settings";
import { AuthenticatedLayout } from "./authenticated-layout/AuthenticatedLayout";
import { UnauthenticatedLayout } from "./unauthenticated-layout/UnauthenticatedLayout";

const Profile = React.lazy(() => import("../pages/profile/Profile"));
const ConfirmEmailChange = React.lazy(() => import("../pages/auth/ConfirmEmailChange"));
const ConfirmRegistration = React.lazy(() => import("../pages/auth/ConfirmRegistration"));
const GroupInvite = React.lazy(() => import("../pages/groups/GroupInvite"));

const router = createBrowserRouter([
{
path: "/",
Expand Down
4 changes: 2 additions & 2 deletions frontend/apps/web/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { api, ws } from "../core/api";
import { selectAuthSlice, selectSettingsSlice, selectTheme, useAppDispatch, useAppSelector } from "../store";
import { Router } from "./Router";

export default function App() {
export const App = () => {
const darkModeSystem = useMediaQuery("(prefers-color-scheme: dark)");
const dispatch = useAppDispatch();
const groupStoreStatus = useAppSelector((state) => state.groups.status);
Expand Down Expand Up @@ -93,4 +93,4 @@ export default function App() {
</ThemeProvider>
</StyledEngineProvider>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { useTheme } from "@mui/material/styles";
import { Banner } from "../../components/style/Banner";
import Loading from "../../components/style/Loading";
import styles from "./AuthenticatedLayout.module.css";
import { LanguageSelect } from "@/components/LanguageSelect";

const drawerWidth = 240;
const AUTH_FALLBACK = "/login";
Expand Down Expand Up @@ -216,6 +217,7 @@ export const AuthenticatedLayout: React.FC = () => {
</RouterLink>
</Typography>
<div>
<LanguageSelect />
<IconButton
aria-label="account of current user"
aria-controls="menu-appbar"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Grid, IconButton, List, ListItem, ListItemText } from "@mui/material";
import ListItemLink from "../../components/style/ListItemLink";
import GroupCreateModal from "../../components/groups/GroupCreateModal";
import React, { useState } from "react";
import { Add } from "@mui/icons-material";
import GroupCreateModal from "@/components/groups/GroupCreateModal";
import ListItemLink from "@/components/style/ListItemLink";
import { selectAuthSlice, selectGroupSlice, useAppSelector } from "@/store";
import { selectGroups, selectIsGuestUser } from "@abrechnung/redux";
import { selectGroupSlice, useAppSelector, selectAuthSlice } from "../../store";
import { Add } from "@mui/icons-material";
import { Grid, IconButton, List, ListItem, ListItemText, Tooltip } from "@mui/material";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";

interface Props {
activeGroupId?: number;
}

export const SidebarGroupList: React.FC<Props> = ({ activeGroupId }) => {
const { t } = useTranslation();
const isGuest = useAppSelector((state) => selectIsGuestUser({ state: selectAuthSlice(state) }));
const groups = useAppSelector((state) => selectGroups({ state: selectGroupSlice(state) }));
const [showGroupCreationModal, setShowGroupCreationModal] = useState(false);
Expand Down Expand Up @@ -45,9 +47,11 @@ export const SidebarGroupList: React.FC<Props> = ({ activeGroupId }) => {
{!isGuest && (
<ListItem sx={{ padding: 0 }}>
<Grid container justifyContent="center">
<IconButton size="small" onClick={openGroupCreateModal}>
<Add />
</IconButton>
<Tooltip title={t("groups.addGroup")}>
<IconButton size="small" onClick={openGroupCreateModal}>
<Add />
</IconButton>
</Tooltip>
</Grid>
</ListItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AppBar, Box, Button, Container, CssBaseline, Toolbar, Typography } from
import { Banner } from "../../components/style/Banner";
import { selectIsAuthenticated } from "@abrechnung/redux";
import { useAppSelector, selectAuthSlice } from "../../store";
import { LanguageSelect } from "@/components/LanguageSelect";

export const UnauthenticatedLayout: React.FC = () => {
const authenticated = useAppSelector((state) => selectIsAuthenticated({ state: selectAuthSlice(state) }));
Expand All @@ -27,6 +28,7 @@ export const UnauthenticatedLayout: React.FC = () => {
Abrechnung
</RouterLink>
</Typography>
<LanguageSelect />
<Button component={RouterLink} color="inherit" to="/login">
Login
</Button>
Expand Down
2 changes: 1 addition & 1 deletion frontend/apps/web/src/components/AccountSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const AccountSelect: React.FC<AccountSelectProps> = ({
options={filteredAccounts}
getOptionLabel={(acc: Account) => acc.name}
multiple={false}
value={value !== undefined ? accounts.find((acc) => acc.id === value) : undefined}
value={value !== undefined ? accounts.find((acc) => acc.id === value) ?? null : null}
disabled={disabled}
openOnFocus
fullWidth
Expand Down
21 changes: 21 additions & 0 deletions frontend/apps/web/src/components/LanguageSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MenuItem, Select, SelectChangeEvent, SelectProps } from "@mui/material";
import * as React from "react";
import { useTranslation } from "react-i18next";

export type LanguageSelectProps = Omit<SelectProps, "value" | "onChange">;

export const LanguageSelect: React.FC<LanguageSelectProps> = (props) => {
const { t, i18n } = useTranslation();

const handleSetLanguage = (event: SelectChangeEvent<unknown>) => {
const lang = event.target.value as string;
i18n.changeLanguage(lang);
};

return (
<Select value={i18n.language} sx={{ color: "inherit" }} onChange={handleSetLanguage} {...props}>
<MenuItem value="en-US">{t("languages.en")}</MenuItem>
<MenuItem value="de-DE">{t("languages.de")}</MenuItem>
</Select>
);
};
Loading

0 comments on commit cef6a5a

Please sign in to comment.