add group creation

This commit is contained in:
2026-01-07 10:25:33 +01:00
parent 090b3c10d1
commit 624e62822c
12 changed files with 330 additions and 94 deletions

View File

@@ -1,5 +1,6 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "bun-react-template", "name": "bun-react-template",
@@ -10,7 +11,8 @@
"@mui/icons-material": "7.3.5", "@mui/icons-material": "7.3.5",
"@mui/material": "7.3.5", "@mui/material": "7.3.5",
"cookie": "^1.0.2", "cookie": "^1.0.2",
"mobx-react-lite": "^4.1.1", "mobx": "^6.15.0",
"mobx-react": "^9.2.1",
"react": "19", "react": "19",
"react-dom": "19", "react-dom": "19",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
@@ -538,6 +540,8 @@
"mobx": ["mobx@6.15.0", "", {}, "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g=="], "mobx": ["mobx@6.15.0", "", {}, "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g=="],
"mobx-react": ["mobx-react@9.2.1", "", { "dependencies": { "mobx-react-lite": "^4.1.1" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-WJNNm0FB2n0Z0u+jS1QHmmWyV8l2WiAj8V8I/96kbUEN2YbYCoKW+hbbqKKRUBqElu0llxM7nWKehvRIkhBVJw=="],
"mobx-react-lite": ["mobx-react-lite@4.1.1", "", { "dependencies": { "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg=="], "mobx-react-lite": ["mobx-react-lite@4.1.1", "", { "dependencies": { "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],

Binary file not shown.

View File

@@ -16,7 +16,8 @@
"@mui/icons-material": "7.3.5", "@mui/icons-material": "7.3.5",
"@mui/material": "7.3.5", "@mui/material": "7.3.5",
"cookie": "^1.0.2", "cookie": "^1.0.2",
"mobx-react-lite": "^4.1.1", "mobx": "^6.15.0",
"mobx-react": "^9.2.1",
"react": "19", "react": "19",
"react-dom": "19", "react-dom": "19",
"react-router-dom": "^7.9.5" "react-router-dom": "^7.9.5"

View File

@@ -1,8 +1,16 @@
import SignIn from './components/SignIn'; import SignIn from './components/authentication/SignIn';
import { createTheme, ThemeProvider } from '@mui/material'; import { createTheme, CssBaseline, ThemeProvider } from '@mui/material';
import { useStore } from './Store'; import { useStore, type View } from './Store';
import Group from './components/Group'; import Group from './components/Group';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { type ReactElement } from 'react';
import NewGroup from './components/authentication/NewGroup';
export const views: Record<View, ReactElement> = {
signIn: <SignIn />,
group: <Group />,
newGroup: <NewGroup />,
};
const App = observer(() => { const App = observer(() => {
const theme = createTheme({ const theme = createTheme({
@@ -12,15 +20,18 @@ const App = observer(() => {
}); });
const store = useStore(); const store = useStore();
const { loggedIn } = store; const { currentView } = store;
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<div className="app"> <CssBaseline enableColorScheme />
{loggedIn ? <div className="app"
<Group /> : style={{
<SignIn /> backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
} backgroundRepeat: 'no-repeat',
}}
>
{views[currentView]}
</div> </div>
</ThemeProvider> </ThemeProvider>
); );

View File

@@ -1,36 +1,64 @@
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import { makeAutoObservable } from 'mobx';
import cookie from 'cookie'; import cookie from 'cookie';
import type { Group, User } from '@/interfaces'; import type { Group, User } from '@/interfaces';
import { makeAutoObservable } from 'mobx';
export type CookieData = { export type CookieData = {
user: User; user: User;
group: Group; group: Group;
}; };
export type View = 'signIn' | 'group' | 'newGroup';
export class Store { export class Store {
cookieData: CookieData | null = null; loggedIn = false;
currentView: View = 'signIn';
user: User | null = null;
group: Group | null = null;
processCookie() { setCurrentView = (view: View) => {
const parsed = cookie.parse(document.cookie); this.currentView = view;
this.cookieData = {
user: parsed.user ? JSON.parse(parsed.user) : null,
group: parsed.group ? JSON.parse(parsed.group) : null,
} as CookieData;
} }
get loggedIn(): boolean { setLoggedIn = (value: boolean) => {
if (this.cookieData === null) { this.loggedIn = value;
return false; }
};
return !!this.cookieData.user && !!this.cookieData.group; setUser = (user: User | null) => {
this.user = user;
}
setGroup = (group: Group | null) => {
this.group = group;
}
logout = () => {
void cookieStore.delete('user');
void cookieStore.delete('group');
this.user = null;
this.group = null;
this.loggedIn = false;
this.currentView = 'signIn';
}
processCookie = () => {
const parsed = cookie.parse(document.cookie);
if (!parsed.user || !parsed.group) {
this.loggedIn = false;
return;
}
this.user = JSON.parse(parsed.user) as User[][0];
this.group = JSON.parse(parsed.group) as Group[][0];
this.loggedIn = !!(this.user && this.group);
} }
constructor() { constructor() {
console.log('Initializing Store');
this.processCookie();
makeAutoObservable(this); makeAutoObservable(this);
this.processCookie();
if (this.loggedIn) {
this.setCurrentView('group');
}
} }
} }

View File

@@ -29,14 +29,12 @@ export default function ForgotPassword({ open, handleClose }: ForgotPasswordProp
}} }}
> >
<DialogTitle>Gruppencode vergessen</DialogTitle> <DialogTitle>Gruppencode vergessen</DialogTitle>
<DialogContent <DialogContent
sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }} sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }}
> >
<DialogContentText> <DialogContentText>
Gib deine E-Mail Adresse ein und wir senden dir eine E-Mail mit deinem Gruppencode zu. Gib deine E-Mail Adresse ein und wir senden dir eine E-Mail mit deinem Gruppencode zu.
</DialogContentText> </DialogContentText>
<OutlinedInput <OutlinedInput
autoFocus autoFocus
required required
@@ -49,10 +47,8 @@ export default function ForgotPassword({ open, handleClose }: ForgotPasswordProp
fullWidth fullWidth
/> />
</DialogContent> </DialogContent>
<DialogActions sx={{ pb: 3, px: 3 }}> <DialogActions sx={{ pb: 3, px: 3 }}>
<Button onClick={handleClose}>Cancel</Button> <Button onClick={handleClose}>Cancel</Button>
<Button variant="contained" type="submit"> <Button variant="contained" type="submit">
Continue Continue
</Button> </Button>

View File

@@ -1,30 +1,26 @@
import * as React from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import Card from './Card'; import Card from './Card';
import { useStore } from '../Store'; import { useStore } from '../Store';
import { CardHeader, IconButton } from '@mui/material';
import LogoutIcon from '@mui/icons-material/Logout';
const Group = () => { const Group = () => {
const [loading, setLoading] = React.useState(false);
const theme = useTheme(); const theme = useTheme();
const store = useStore(); const store = useStore();
const { group } = store.cookieData!; const { group, logout } = store;
console.log('Group component rendered with group:', group.name); if (!group) {
return (<Typography>Keine Gruppe gefunden.</Typography>);
}
return ( return (
<div style={{ <div>
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
backgroundRepeat: 'no-repeat',
}}>
<CssBaseline enableColorScheme />
<Stack direction="column" justifyContent="space-between" <Stack direction="column" justifyContent="space-between"
sx={{ sx={{
height: 'calc((1 - var(--template-frame-height, 0)) * 100dvh)', height: '100dvh',
minHeight: '100%', minHeight: '100%',
padding: theme.spacing(2), padding: theme.spacing(2),
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
@@ -33,13 +29,14 @@ const Group = () => {
}} }}
> >
<Card variant="outlined"> <Card variant="outlined">
<Typography <CardHeader
component="h1" action={
variant="h4" <IconButton aria-label="logout" onClick={logout}>
sx={{ width: '100%', fontSize: 'clamp(2rem, 10vw, 2.15rem)' }} <LogoutIcon />
> </IconButton>
{group.name} }
</Typography> title={group.name}
/>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',

View File

@@ -0,0 +1,196 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import FormLabel from '@mui/material/FormLabel';
import FormControl from '@mui/material/FormControl';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles';
import ForgotPassword from '../ForgotPassword';
import Card from '../Card';
import { createGroup, createUser, fetchUser } from '../../serverApi';
import { observer } from 'mobx-react-lite';
import { useStore } from '../../Store';
import LoginIcon from '@mui/icons-material/Login';
const NewGroup = observer(() => {
const [emailInput, setEmailInput] = React.useState('');
const [emailError, setEmailError] = React.useState('');
const [groupInput, setGroupINput] = React.useState('');
const [groupError, setGroupError] = React.useState('');
const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const {setLoggedIn, setCurrentView, setGroup, setUser} = useStore();
const theme = useTheme();
React.useEffect(() => {
const params = new URLSearchParams(window.location.search);
const groupParam = params.get('gruppe');
if (groupParam) {
setGroupINput(groupParam);
}
}, []);
const handleClose = () => {
setOpen(false);
};
const handleCreateGroup = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
if (!isMailValid() || !isGroupValid()) return;
setLoading(true);
try {
// handle user
let user = await fetchUser(emailInput);
if (!user) {
user = await createUser(emailInput);
if (!user) throw new Error('Error creating user');
}
// handle group
const groupData = await createGroup({ name: groupInput, mail: emailInput });
if (!groupData) throw new Error('Error creating group');
await cookieStore.set('user', JSON.stringify(user));
await cookieStore.set('group', JSON.stringify(groupData));
setUser(user);
setGroup(groupData);
setLoggedIn(true);
setCurrentView('group');
}
catch (error) {
console.error('Error during group creation:', error);
} finally {
setLoading(false);
}
};
const isMailValid = (): boolean => {
let isValid = true;
if (emailInput === '') { return true; }
if (!/\S+@\S+\.\S+/.test(emailInput)) {
setEmailError('Bitte gib eine gültige E-Mail Adresse ein.');
isValid = false;
} else {
setEmailError('');
}
return isValid;
};
const isGroupValid = (): boolean => {
let isValid = true;
if (groupInput === '') { return true; }
if (!/^[A-Z0-9]{6,}$/.test(groupInput)) {
setGroupError('Bitte gib einen gültigen Gruppenname ein. [A-Z, 0-9, mindestens 6 Zeichen]');
isValid = false;
} else {
setGroupError('');
}
return isValid;
};
const handleClickSignIn = () => {
setCurrentView('signIn');
};
return (
<div>
<Stack direction="column" justifyContent="space-between"
sx={{
height: '100dvh',
minHeight: '100%',
padding: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
padding: theme.spacing(4),
},
}}
>
<Card variant="outlined">
<Typography
component="h1"
variant="h4"
sx={{ width: '100%', fontSize: 'clamp(2rem, 10vw, 2.15rem)' }}
>
{'Neue Gruppe erstellen'}
</Typography>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
gap: 2,
}}
>
<FormControl>
<FormLabel htmlFor="email">E-Mail des Gruppen-Administrator</FormLabel>
<TextField
error={emailError !== ''}
helperText={emailError}
id="email"
type="email"
name="email"
placeholder="ren@tier.de"
autoComplete="email"
required
fullWidth
variant="outlined"
onBlur={isMailValid}
onChange={event => setEmailInput(event.target.value.trim())}
value={emailInput}
color={emailError ? 'error' : 'primary'} />
</FormControl>
<FormControl>
<FormLabel>Gruppenname</FormLabel>
<TextField
error={groupError !== ''}
helperText={groupError}
name="groupcode"
placeholder="GRUPPE123"
type="text"
id="groupcode"
autoComplete="current-groupcode"
required
fullWidth
variant="outlined"
onBlur={() => void isGroupValid()}
onChange={event => setGroupINput(event.target.value.toUpperCase())}
value={groupInput}
color={groupError ? 'error' : 'primary'}
/>
</FormControl>
<ForgotPassword open={open} handleClose={handleClose} />
<Button
fullWidth
variant="contained"
onClick={event => {void handleCreateGroup(event)}}
loading={loading}
>
{'Gruppe erstellen'}
</Button>
</Box>
<Divider>oder</Divider>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Button
fullWidth
variant="outlined"
onClick={handleClickSignIn}
startIcon={<LoginIcon />}
>
{'Gruppe beitreten'}
</Button>
</Box>
</Card>
</Stack>
</div>
);
});
export default NewGroup;

View File

@@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
import FormLabel from '@mui/material/FormLabel'; import FormLabel from '@mui/material/FormLabel';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
@@ -11,26 +10,28 @@ import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import GroupAddIcon from '@mui/icons-material/GroupAdd'; import GroupAddIcon from '@mui/icons-material/GroupAdd';
import ForgotPassword from './ForgotPassword'; import ForgotPassword from '../ForgotPassword';
import Card from './Card'; import Card from '../Card';
import { createUser, fetchGroupByCode, fetchUser } from '../serverApi'; import { createUser, fetchGroupByCode, fetchUser } from '../../serverApi';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { useStore } from '../../Store';
const SignIn = observer(() => { const SignIn = observer(() => {
const [email, setEmail] = React.useState(''); const [emailInput, setEmailInput] = React.useState('');
const [emailError, setEmailError] = React.useState(''); const [emailError, setEmailError] = React.useState('');
const [group, setGroup] = React.useState(''); const [groupInput, setGroupInput] = React.useState('');
const [groupError, setGroupError] = React.useState(''); const [groupError, setGroupError] = React.useState('');
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const {setLoggedIn, setCurrentView, setGroup, setUser} = useStore();
const theme = useTheme(); const theme = useTheme();
React.useEffect(() => { React.useEffect(() => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const groupParam = params.get('gruppe'); const groupParam = params.get('gruppe');
if (groupParam) { if (groupParam) {
setGroup(groupParam); setGroupInput(groupParam);
} }
}, []); }, []);
@@ -49,23 +50,27 @@ const SignIn = observer(() => {
setLoading(true); setLoading(true);
try { try {
const groupData = await fetchGroupByCode(group); const groupData = await fetchGroupByCode(groupInput);
if (!groupData) { if (!groupData) {
setGroupError('Gruppencode existiert nicht.'); setGroupError('Gruppencode existiert nicht.');
return; return;
} }
let user = await fetchUser(email); let user = await fetchUser(emailInput);
if (!user) { if (!user) {
user = await createUser(email); user = await createUser(emailInput);
if (!user) throw new Error('Error creating user'); if (!user) throw new Error('Error creating user');
} }
await cookieStore.set('user', JSON.stringify(user)); await cookieStore.set('user', JSON.stringify(user));
await cookieStore.set('group', JSON.stringify(groupData)); await cookieStore.set('group', JSON.stringify(groupData));
setGroup(groupData);
setUser(user);
setLoggedIn(true);
setCurrentView('group');
} }
catch (error) { catch (error) {
console.error('Error during sign-in process:', error); console.error('Error during sign-in process:', error);
@@ -75,7 +80,7 @@ const SignIn = observer(() => {
const isMailValid = (): boolean => { const isMailValid = (): boolean => {
let isValid = true; let isValid = true;
if (!/\S+@\S+\.\S+/.test(email)) { if (!/\S+@\S+\.\S+/.test(emailInput)) {
setEmailError('Bitte gib eine gültige E-Mail Adresse ein.'); setEmailError('Bitte gib eine gültige E-Mail Adresse ein.');
isValid = false; isValid = false;
} else { } else {
@@ -86,7 +91,7 @@ const SignIn = observer(() => {
const isGroupValid = (): boolean => { const isGroupValid = (): boolean => {
let isValid = true; let isValid = true;
if (!/^[A-Z0-9]{6}$/.test(group)) { if (!/^[A-Z0-9]{6}$/.test(groupInput)) {
setGroupError('Bitte gib einen gültigen Gruppencode ein.'); setGroupError('Bitte gib einen gültigen Gruppencode ein.');
isValid = false; isValid = false;
} else { } else {
@@ -95,15 +100,15 @@ const SignIn = observer(() => {
return isValid; return isValid;
}; };
const handleClickNewGroup = () => {
setCurrentView('newGroup');
}
return ( return (
<div style={{ <div>
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
backgroundRepeat: 'no-repeat',
}}>
<CssBaseline enableColorScheme />
<Stack direction="column" justifyContent="space-between" <Stack direction="column" justifyContent="space-between"
sx={{ sx={{
height: 'calc((1 - var(--template-frame-height, 0)) * 100dvh)', height: '100dvh',
minHeight: '100%', minHeight: '100%',
padding: theme.spacing(2), padding: theme.spacing(2),
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
@@ -137,13 +142,12 @@ const SignIn = observer(() => {
name="email" name="email"
placeholder="ren@tier.de" placeholder="ren@tier.de"
autoComplete="email" autoComplete="email"
autoFocus
required required
fullWidth fullWidth
variant="outlined" variant="outlined"
onBlur={isMailValid} onBlur={isMailValid}
onChange={event => setEmail(event.target.value)} onChange={event => setEmailInput(event.target.value)}
value={email} value={emailInput}
color={emailError ? 'error' : 'primary'} /> color={emailError ? 'error' : 'primary'} />
</FormControl> </FormControl>
<FormControl> <FormControl>
@@ -160,8 +164,8 @@ const SignIn = observer(() => {
fullWidth fullWidth
variant="outlined" variant="outlined"
onBlur={isGroupValid} onBlur={isGroupValid}
onChange={event => setGroup(event.target.value)} onChange={event => setGroupInput(event.target.value.toUpperCase())}
value={group} value={groupInput}
color={groupError ? 'error' : 'primary'} /> color={groupError ? 'error' : 'primary'} />
</FormControl> </FormControl>
<ForgotPassword open={open} handleClose={handleClose} /> <ForgotPassword open={open} handleClose={handleClose} />
@@ -188,7 +192,7 @@ const SignIn = observer(() => {
<Button <Button
fullWidth fullWidth
variant="outlined" variant="outlined"
onClick={() => alert('Sign in with Google')} onClick={handleClickNewGroup}
startIcon={<GroupAddIcon />} startIcon={<GroupAddIcon />}
> >
{'Neue Gruppe erstellen'} {'Neue Gruppe erstellen'}

View File

@@ -34,7 +34,7 @@ export async function fetchGroupByCode(code: string): Promise<Group | null> {
try { try {
const res = await fetch(`/api/group/${code}`); const res = await fetch(`/api/group/${code}`);
if (!res.ok) return null; if (!res.ok) return null;
const data: Group = await res.json(); const data: Group | null = await res.json();
return data; return data;
} catch (err) { } catch (err) {
console.error('Failed to fetch group:', err); console.error('Failed to fetch group:', err);
@@ -42,14 +42,14 @@ export async function fetchGroupByCode(code: string): Promise<Group | null> {
} }
} }
export async function createGroup(group: Group): Promise<Group | null> { export async function createGroup(group: Partial<Group>): Promise<Group | null> {
try { try {
const res = await fetch('/api/group', { const res = await fetch('/api/group', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ group }), body: JSON.stringify(group),
}); });
if (!res.ok) return null; if (!res.ok) return null;
const data: Group = await res.json(); const data: Group = await res.json();

View File

@@ -14,7 +14,6 @@ const server = serve({
console.log('Received request for user:', mail); console.log('Received request for user:', mail);
const user = await db.getUserByMail(mail); const user = await db.getUserByMail(mail);
console.log('Fetching user with mail:', mail, 'Result:', user); console.log('Fetching user with mail:', mail, 'Result:', user);
if (!user) return new Response(JSON.stringify({ error: 'User not found' }), { status: 404 });
return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } }); return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } });
}, },
@@ -37,7 +36,6 @@ const server = serve({
console.log('Received request for group:', code); console.log('Received request for group:', code);
const group = await db.getGroupByCode(code); const group = await db.getGroupByCode(code);
console.log('Fetching group with ID:', code, 'Result:', group); console.log('Fetching group with ID:', code, 'Result:', group);
if (!group) return new Response(JSON.stringify({ error: 'Group not found' }), { status: 404 });
return new Response(JSON.stringify(group), { headers: { 'Content-Type': 'application/json' } }); return new Response(JSON.stringify(group), { headers: { 'Content-Type': 'application/json' } });
}, },
@@ -46,7 +44,7 @@ const server = serve({
try { try {
const { name, mail } = await req.json() as { name: string; mail: string }; const { name, mail } = await req.json() as { name: string; mail: string };
console.log('Received request to create group with name:', name, 'and mail:', mail); console.log('Received request to create group with name:', name, 'and mail:', mail);
const response = db.createGroup({ name, mail }); const response = await db.createGroup({ name, mail });
return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } });
} catch (err) { } catch (err) {
console.error('Error creating group:', err); console.error('Error creating group:', err);

View File

@@ -80,25 +80,25 @@ class DB {
* @param mail: string * @param mail: string
* @returns created user * @returns created user
*/ */
public async createUser(mail: string): Promise<User> { public async createUser(mail: string): Promise<User | null> {
const user: User = await this.instance` const user: User[] = await this.instance`
INSERT INTO users (mail) VALUES (${mail}) INSERT INTO users (mail) VALUES (${mail})
RETURNING * RETURNING *
`; `;
return user; return user[0] ?? null;
} }
/** /**
* Get user by mail * Get user by mail
* @param mail: string * @param mail: string
* @returns user object or undefined * @returns user object or null
*/ */
public async getUserByMail(mail: string): Promise<User | undefined> { public async getUserByMail(mail: string): Promise<User | null> {
const user: User = await this.instance` const user: User[] = await this.instance`
SELECT * FROM users WHERE mail = ${mail} SELECT * FROM users WHERE mail = ${mail}
`; `;
return user; return user[0] ?? null;
} }
/* GROUPS */ /* GROUPS */
@@ -109,25 +109,26 @@ class DB {
* @param mail: string * @param mail: string
* @returns object with id of the created group * @returns object with id of the created group
*/ */
public async createGroup( {name, mail}: {name: string, mail: string}): Promise<Group> { public async createGroup( {name, mail}: {name: string, mail: string}): Promise<Group | null> {
const code = Math.random().toString(36).substring(2, 8).toUpperCase(); const code = Math.random().toString(36).substring(2, 8).toUpperCase();
const group: Group = await this.instance` const group: Group[] = await this.instance`
INSERT INTO groups (code, name, mail) VALUES (${code}, ${name}, ${mail}) INSERT INTO groups (code, name, mail) VALUES (${code}, ${name}, ${mail})
RETURNING * RETURNING *
`; `;
return group; console.log('Created group:', group);
return group[0] ?? null;
}; };
/** /**
* Get group by ID * Get group by ID
* @param id: string * @param id: string
* @returns group object or undefined * @returns group object or null
*/ */
public async getGroupByCode(code: string): Promise<Group | undefined> { public async getGroupByCode(code: string): Promise<Group | null> {
const group: Group | undefined = await this.instance` const group: Group[] = await this.instance`
SELECT * FROM groups WHERE code = ${code} SELECT * FROM groups WHERE code = ${code}
`; `;
return group; return group[0] ?? null;
} }
/* GROUP MEMBER */ /* GROUP MEMBER */