From 8940fafac04a8423a1006210c41d07270cfa4b94 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:28:46 +0200 Subject: [PATCH 01/93] fix: restructure logos --- README.md | 8 ++- messages/en.json | 3 +- messages/no.json | 3 +- .../[locale]/(default)/news/(header)/page.tsx | 11 ++-- src/components/assets/logos/FeideLogo.tsx | 60 +++++++++++++++++++ .../{Logo.tsx => logos/HackerspaceLogo.tsx} | 4 +- .../assets/{sponsors => logos}/IDILogo.tsx | 0 .../assets/{sponsors => logos}/NexusLogo.tsx | 0 .../assets/{sponsors => logos}/index.tsx | 1 + src/components/layout/Footer.tsx | 4 +- src/components/layout/Header.tsx | 1 + src/components/layout/LogoLink.tsx | 4 +- src/components/news/CardGrid.tsx | 4 +- src/components/news/ItemGrid.tsx | 4 +- src/components/settings/ProfileMenu.tsx | 1 - 15 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 src/components/assets/logos/FeideLogo.tsx rename src/components/assets/{Logo.tsx => logos/HackerspaceLogo.tsx} (97%) rename src/components/assets/{sponsors => logos}/IDILogo.tsx (100%) rename src/components/assets/{sponsors => logos}/NexusLogo.tsx (100%) rename src/components/assets/{sponsors => logos}/index.tsx (61%) diff --git a/README.md b/README.md index 11fbb20c..682d61f0 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Here is a list of documentation to help you get started: - [Next-intl](https://next-intl-docs.vercel.app/) - Internationalization library - [nuqs](https://nuqs.47ng.com/docs/installation) - Easy to use query params - [BlockNote](https://www.blocknotejs.org/docs) - Tool for markdown textboxes -- [Tanstack Form](https://tanstack.com/form/latest/docs/overview) - When we need to handle form validation (shadcn/ui uses react-hook-form. but I think this is better, we will figure it out) -- [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/overview) - TRPC wraps Tanstack Query which is how we fetch data from the backend +- [React Hook Form](https://react-hook-form.com/get-started) - When we need to handle form validation #### Styling @@ -24,7 +23,8 @@ Here is a list of documentation to help you get started: ### Backend -- [TRPC](https://trpc.io/docs) - Tool for creating API endpoints as functions +- [TRPC](https://trpc.io/docs/client/react/server-components) - Tool for creating API endpoints as functions + - [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#streaming-with-server-components) - TRPC wraps Tanstack Query which is how we fetch data from the backend on the client - [Lucia](https://lucia-auth.com) - Authentication library - [Drizzle](https://orm.drizzle.team/docs/overview) - ORM for interacting with the database (Postgres under the hood) - [s3-client](https://github.com/aws/aws-sdk-js-v3/tree/main/clients/client-s3) - AWS S3 client for uploading files @@ -116,3 +116,5 @@ We are using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0. - All layout components should end with Layout. For example: `DefaultLayout`. - All page components should end with Page to make it clear it is a whole page. For example: `AboutPage`. +- All icons should end with Icon. For example: `HomeIcon`. +- React components should be named with PascalCase. For example: `Button`. The filename should match the component name. diff --git a/messages/en.json b/messages/en.json index 316e4ba6..9acbcb20 100644 --- a/messages/en.json +++ b/messages/en.json @@ -39,7 +39,8 @@ "visitInstagram": "Visit our Instagram", "NTNUIDI": "NTNU Department of Computer Science", "NTNUIDIURL": "https://www.ntnu.edu/idi", - "NTNUKiD": "The working life network KID", + "NTNUNexus": "The working life network Nexus", + "feideInlogin": "Login with Feide", "copyright": "Copyright", "allRightsReserved": "All rights reserved" }, diff --git a/messages/no.json b/messages/no.json index 79286cbf..5c183425 100644 --- a/messages/no.json +++ b/messages/no.json @@ -39,7 +39,8 @@ "visitInstagram": "Besøk Instagram", "NTNUIDI": "NTNU Institutt for datateknologi og informatikk", "NTNUIDIURL": "https://www.ntnu.no/idi", - "NTNUKiD": "Arbeidslivsnettverket KID", + "NTNUNexus": "Arbeidslivsnettverket Nexus", + "feideLogin": "Logg in med Feide", "copyright": "Opphavsrett", "allRightsReserved": "Alle rettigheter reservert" }, diff --git a/src/app/[locale]/(default)/news/(header)/page.tsx b/src/app/[locale]/(default)/news/(header)/page.tsx index 30444ab8..26bdf531 100644 --- a/src/app/[locale]/(default)/news/(header)/page.tsx +++ b/src/app/[locale]/(default)/news/(header)/page.tsx @@ -1,14 +1,13 @@ -import { articleMockData as articleData } from '@/mock-data/article'; -import { useTranslations } from 'next-intl'; -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import { createSearchParamsCache, parseAsInteger } from 'nuqs/server'; -import { Suspense } from 'react'; - import { PaginationCarousel } from '@/components/layout/PaginationCarousel'; import { CardGrid } from '@/components/news/CardGrid'; import { ItemGrid } from '@/components/news/ItemGrid'; import { ItemGridSkeleton } from '@/components/news/ItemGridSkeleton'; import { Separator } from '@/components/ui/Separator'; +import { articleMockData as articleData } from '@/mock-data/article'; +import { useTranslations } from 'next-intl'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; +import { createSearchParamsCache, parseAsInteger } from 'nuqs/server'; +import { Suspense } from 'react'; export async function generateMetadata({ params: { locale }, diff --git a/src/components/assets/logos/FeideLogo.tsx b/src/components/assets/logos/FeideLogo.tsx new file mode 100644 index 00000000..0baa22a6 --- /dev/null +++ b/src/components/assets/logos/FeideLogo.tsx @@ -0,0 +1,60 @@ +import { cx } from '@/lib/utils'; + +function FeideLogo({ + className, + title, + ...rest +}: { + className?: string; + title: string; +}) { + return ( + + + + + + + + + + + + + + ); +} + +export { FeideLogo }; diff --git a/src/components/assets/Logo.tsx b/src/components/assets/logos/HackerspaceLogo.tsx similarity index 97% rename from src/components/assets/Logo.tsx rename to src/components/assets/logos/HackerspaceLogo.tsx index a8a9f090..665bcb43 100644 --- a/src/components/assets/Logo.tsx +++ b/src/components/assets/logos/HackerspaceLogo.tsx @@ -1,4 +1,4 @@ -function Logo({ className, ...rest }: { className?: string }) { +function HackerspaceLogo({ className, ...rest }: { className?: string }) { return ( - + diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index c81eba8b..d54c66ac 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -48,6 +48,7 @@ function Header() { t={{ profile: t('profile'), signIn: t('signIn'), + feideLogin: t('feideLogin'), }} /> diff --git a/src/components/layout/LogoLink.tsx b/src/components/layout/LogoLink.tsx index 23018e8e..2610952c 100644 --- a/src/components/layout/LogoLink.tsx +++ b/src/components/layout/LogoLink.tsx @@ -1,4 +1,4 @@ -import { Logo } from '@/components/assets/Logo'; +import { HackerspaceLogo } from '@/components/assets/logos'; import { Button } from '@/components/ui/Button'; import { Link } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; @@ -20,7 +20,7 @@ function LogoLink({ size='none' > - + HACKERSPACE diff --git a/src/components/news/CardGrid.tsx b/src/components/news/CardGrid.tsx index 46156cd5..c56adf37 100644 --- a/src/components/news/CardGrid.tsx +++ b/src/components/news/CardGrid.tsx @@ -1,4 +1,5 @@ import { ArticleCard } from '@/components/news/ArticleCard'; +import { api } from '@/lib/api/server'; import { cx } from '@/lib/utils'; type CardGridProps = { topArticles: { @@ -10,7 +11,8 @@ type CardGridProps = { }[]; }; -function CardGrid({ topArticles }: CardGridProps) { +async function CardGrid({ topArticles }: CardGridProps) { + const hello = await api.test.helloWorld(); return (
{topArticles.map((data, index) => ( diff --git a/src/components/news/ItemGrid.tsx b/src/components/news/ItemGrid.tsx index 6a610f6d..25430db4 100644 --- a/src/components/news/ItemGrid.tsx +++ b/src/components/news/ItemGrid.tsx @@ -1,13 +1,15 @@ import { ArticleItem } from '@/components/news/ArticleItem'; +import { api } from '@/lib/api/server'; import { articleMockData as articleData } from '@/mock-data/article'; type ItemGridProps = { page: number; }; -function ItemGrid({ page }: ItemGridProps) { +async function ItemGrid({ page }: ItemGridProps) { const itemsDisplayedAsCards = 4; const itemsPerPage = 6; + const hello = await api.test.helloWorld(); const start = (page - 1) * itemsPerPage + itemsDisplayedAsCards; const end = start + itemsPerPage; diff --git a/src/components/settings/ProfileMenu.tsx b/src/components/settings/ProfileMenu.tsx index 446f65e7..13c9b860 100644 --- a/src/components/settings/ProfileMenu.tsx +++ b/src/components/settings/ProfileMenu.tsx @@ -11,7 +11,6 @@ import { UserIcon } from 'lucide-react'; import * as React from 'react'; function ProfileMenu({ t }: { t: { profile: string; signIn: string } }) { - // TODO: User Icon Color should only have the primary color when logged in return ( From f01754d84b85ddc467612e50f4ca1ce9313dcbd9 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:05:48 +0200 Subject: [PATCH 02/93] fix news skeleton text skeleton --- src/components/news/ArticleItemSkeleton.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/news/ArticleItemSkeleton.tsx b/src/components/news/ArticleItemSkeleton.tsx index 5037918f..f3568515 100644 --- a/src/components/news/ArticleItemSkeleton.tsx +++ b/src/components/news/ArticleItemSkeleton.tsx @@ -7,8 +7,10 @@ function ArticleItemSkeleton() {
- - + +
+ +
); From c47a4f4f01542395da6c4bb80759cbbe981c5de2 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:07:24 +0200 Subject: [PATCH 03/93] feat: initial auth page --- messages/en.json | 5 ++++ messages/no.json | 5 ++++ src/app/[locale]/login/layout.tsx | 20 +++++++++++++++ src/app/[locale]/login/page.tsx | 40 ++++++++++++++++++++++++++++++ src/components/layout/LogoLink.tsx | 13 ++++++++-- src/components/layout/Main.tsx | 15 +++++------ src/lib/locale/index.ts | 4 +++ 7 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 src/app/[locale]/login/layout.tsx create mode 100644 src/app/[locale]/login/page.tsx diff --git a/messages/en.json b/messages/en.json index 316e4ba6..69eba9ec 100644 --- a/messages/en.json +++ b/messages/en.json @@ -14,6 +14,7 @@ "layout": { "hackerspaceHome": "Hackerspace homepage", "navigationMenu": "Navigation menu", + "login": "Login", "news": "News", "events": "Events", "storage": "Storage", @@ -43,6 +44,10 @@ "copyright": "Copyright", "allRightsReserved": "All rights reserved" }, + "login": { + "welcome": "Welcome!", + "description": "Is it your first time here? Login with Feide" + }, "news": { "title": "News", "internalArticle": "This is an internal article", diff --git a/messages/no.json b/messages/no.json index 79286cbf..031c983c 100644 --- a/messages/no.json +++ b/messages/no.json @@ -14,6 +14,7 @@ "layout": { "hackerspaceHome": "Hackerspace hjemmeside", "navigationMenu": "Navigasjonsmeny", + "login": "Logg inn", "news": "Nyheter", "events": "Hendelser", "storage": "Lager", @@ -43,6 +44,10 @@ "copyright": "Opphavsrett", "allRightsReserved": "Alle rettigheter reservert" }, + "login": { + "welcome": "Velkommen!", + "description": "Er du her for første gang? Logg inn med Feide" + }, "news": { "title": "Nyheter", "internalArticle": "Dette er en intern artikkel", diff --git a/src/app/[locale]/login/layout.tsx b/src/app/[locale]/login/layout.tsx new file mode 100644 index 00000000..361abaad --- /dev/null +++ b/src/app/[locale]/login/layout.tsx @@ -0,0 +1,20 @@ +import { Main } from '@/components/layout/Main'; +import { Card } from '@/components/ui/Card'; +import { unstable_setRequestLocale } from 'next-intl/server'; + +type DefaultLayoutProps = { + children: React.ReactNode; + params: { locale: string }; +}; + +export default function LoginLayout({ + children, + params: { locale }, +}: DefaultLayoutProps) { + unstable_setRequestLocale(locale); + return ( +
+ {children} +
+ ); +} diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx new file mode 100644 index 00000000..c0944de8 --- /dev/null +++ b/src/app/[locale]/login/page.tsx @@ -0,0 +1,40 @@ +import { LogoLink } from '@/components/layout/LogoLink'; +import { CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'; +import { Separator } from '@radix-ui/react-dropdown-menu'; +import { useTranslations } from 'next-intl'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; + +export async function generateMetadata({ + params: { locale }, +}: { + params: { locale: string }; +}) { + const t = await getTranslations({ locale, namespace: 'layout' }); + + return { + title: t('login'), + }; +} + +export default function AboutPage({ + params: { locale }, +}: { + params: { locale: string }; +}) { + unstable_setRequestLocale(locale); + const t = useTranslations('login'); + return ( + <> + + + {t('welcome')} + {t('description')} + + + + ); +} diff --git a/src/components/layout/LogoLink.tsx b/src/components/layout/LogoLink.tsx index dfddc11d..f85e99ee 100644 --- a/src/components/layout/LogoLink.tsx +++ b/src/components/layout/LogoLink.tsx @@ -6,9 +6,13 @@ import { useTranslations } from 'next-intl'; function LogoLink({ className, + logoClassName, + titleClassName, onClick, }: { className?: string; + logoClassName?: string; + titleClassName?: string; onClick?: () => void; }) { const t = useTranslations('layout'); @@ -20,8 +24,13 @@ function LogoLink({ size='none' > - - + + HACKERSPACE diff --git a/src/components/layout/Main.tsx b/src/components/layout/Main.tsx index c148e02f..30bc84e7 100644 --- a/src/components/layout/Main.tsx +++ b/src/components/layout/Main.tsx @@ -1,20 +1,17 @@ import { cx } from '@/lib/utils'; -type MainProps = { - children: React.ReactNode; - className?: string; -}; - -function Main({ children, className }: MainProps) { +function Main({ + className, + ...props +}: { className?: string } & React.HTMLAttributes) { return (
- {children} -
+ {...props} + /> ); } diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 20bda14d..3e341d8e 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -9,6 +9,10 @@ export const routing = defineRouting({ localePrefix: 'as-needed', pathnames: { '/': '/', + '/login': { + en: '/login', + no: '/logg-inn', + }, '/events': { en: '/events', no: '/arrangementer', From f4d6990401a5b6f746211d1a6963b018e4beeb41 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:21:59 +0200 Subject: [PATCH 04/93] chore: merge with old auth branch --- src/app/[locale]/login/page.tsx | 8 ++++---- src/components/layout/LogoLink.tsx | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx index c0944de8..713f6f14 100644 --- a/src/app/[locale]/login/page.tsx +++ b/src/app/[locale]/login/page.tsx @@ -1,6 +1,6 @@ import { LogoLink } from '@/components/layout/LogoLink'; import { CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'; -import { Separator } from '@radix-ui/react-dropdown-menu'; +import { Separator } from '@/components/ui/Separator'; import { useTranslations } from 'next-intl'; import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; @@ -28,13 +28,13 @@ export default function AboutPage({ {t('welcome')} {t('description')} - + ); } diff --git a/src/components/layout/LogoLink.tsx b/src/components/layout/LogoLink.tsx index a85c497f..fd83091e 100644 --- a/src/components/layout/LogoLink.tsx +++ b/src/components/layout/LogoLink.tsx @@ -24,10 +24,12 @@ function LogoLink({ size='none' > - + From c1bac6775b10e1eab51cb9318a837905a42a2bde Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:46:55 +0200 Subject: [PATCH 05/93] feat: add login page frontend --- messages/en.json | 5 ++-- messages/no.json | 5 ++-- src/app/[locale]/login/layout.tsx | 2 +- src/app/[locale]/login/page.tsx | 30 ++++++++++++++++++++--- src/components/assets/logos/FeideLogo.tsx | 2 +- src/components/layout/Footer.tsx | 2 +- 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/messages/en.json b/messages/en.json index 8ec2ccff..0c783ab0 100644 --- a/messages/en.json +++ b/messages/en.json @@ -49,13 +49,14 @@ "NTNUIDI": "NTNU Department of Computer Science", "NTNUIDIURL": "https://www.ntnu.edu/idi", "NTNUNexus": "The working life network Nexus", - "feideInlogin": "Login with Feide", "copyright": "Copyright", "allRightsReserved": "All rights reserved" }, "login": { "welcome": "Welcome!", - "description": "Is it your first time here? Login with Feide" + "description": "Is it your first time here? Use Feide", + "loginWith": "Login with", + "hackerspaceAccount": "Hackerspace Account" }, "news": { "title": "News", diff --git a/messages/no.json b/messages/no.json index d285f983..338a0ad2 100644 --- a/messages/no.json +++ b/messages/no.json @@ -49,13 +49,14 @@ "NTNUIDI": "NTNU Institutt for datateknologi og informatikk", "NTNUIDIURL": "https://www.ntnu.no/idi", "NTNUNexus": "Arbeidslivsnettverket Nexus", - "feideLogin": "Logg in med Feide", "copyright": "Opphavsrett", "allRightsReserved": "Alle rettigheter reservert" }, "login": { "welcome": "Velkommen!", - "description": "Er du her for første gang? Logg inn med Feide" + "description": "Er du her for første gang? Bruk Feide", + "loginWith": "Logg inn med", + "hackerspaceAccount": "Hackerspace-konto" }, "news": { "title": "Nyheter", diff --git a/src/app/[locale]/login/layout.tsx b/src/app/[locale]/login/layout.tsx index 361abaad..da755140 100644 --- a/src/app/[locale]/login/layout.tsx +++ b/src/app/[locale]/login/layout.tsx @@ -14,7 +14,7 @@ export default function LoginLayout({ unstable_setRequestLocale(locale); return (
- {children} + {children}
); } diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx index 713f6f14..3990dd08 100644 --- a/src/app/[locale]/login/page.tsx +++ b/src/app/[locale]/login/page.tsx @@ -1,6 +1,14 @@ +import { FeideLogo } from '@/components/assets/logos/FeideLogo'; import { LogoLink } from '@/components/layout/LogoLink'; -import { CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'; +import { Button } from '@/components/ui/Button'; +import { + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/Card'; import { Separator } from '@/components/ui/Separator'; +import { FingerprintIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; @@ -27,14 +35,28 @@ export default function AboutPage({ <> {t('welcome')} - {t('description')} + + {t('description')} + - + +
+ +

{t('loginWith')}

+
+ + +
); } diff --git a/src/components/assets/logos/FeideLogo.tsx b/src/components/assets/logos/FeideLogo.tsx index 0baa22a6..998b8dc4 100644 --- a/src/components/assets/logos/FeideLogo.tsx +++ b/src/components/assets/logos/FeideLogo.tsx @@ -15,7 +15,7 @@ function FeideLogo({ height='100%' viewBox='0 0 114 39' xmlns='http://www.w3.org/2000/svg' - aria-describedby='feielogo' + aria-describedby='feidelogo' {...rest} > diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 644612c7..ff8b3634 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -192,7 +192,7 @@ function Footer() {
  • - + ); } diff --git a/src/app/[locale]/not-found.tsx b/src/app/[locale]/not-found.tsx index e97706fa..7ad002a8 100644 --- a/src/app/[locale]/not-found.tsx +++ b/src/app/[locale]/not-found.tsx @@ -1,3 +1,4 @@ +import { Main } from '@/components/layout/Main'; import { Button } from '@/components/ui/Button'; import { Link } from '@/lib/locale/navigation'; import { HardDriveIcon } from 'lucide-react'; @@ -6,7 +7,7 @@ import { useTranslations } from 'next-intl'; export default function NotFoundPage() { const t = useTranslations('error'); return ( -
    +

    {t('notFound')} @@ -17,6 +18,6 @@ export default function NotFoundPage() { -

    + ); } From c3e83f786f747e503f6da9c44980910d333ab2d1 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:03:18 +0200 Subject: [PATCH 12/93] feat: tailwindcss plugins --- bun.lockb | Bin 222872 -> 224012 bytes docker-compose.yml => compose.yml | 0 package.json | 12 +++++++----- postcss.config.cjs => postcss.config.js | 3 ++- src/components/layout/Main.tsx | 2 +- src/components/ui/Command.tsx | 2 +- src/components/ui/Dialog.tsx | 6 +++--- src/components/ui/DropdownMenu.tsx | 12 ++++++------ src/components/ui/Popover.tsx | 2 +- src/components/ui/Select.tsx | 6 +++--- src/components/ui/Sheet.tsx | 14 +++++++------- src/components/ui/Tooltip.tsx | 2 +- tailwind.config.ts | 4 ++++ tsconfig.json | 4 ++-- 14 files changed, 38 insertions(+), 31 deletions(-) rename docker-compose.yml => compose.yml (100%) rename postcss.config.cjs => postcss.config.js (52%) diff --git a/bun.lockb b/bun.lockb index c4e9ff5eb6be3c0c986e277d897b23d3a6297df5..dc6a104041a5e9ce704be630781bf34394fab66e 100755 GIT binary patch delta 46100 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 S7{x`Ui{rq{C1S(mcm4z;UlT3> delta 45493 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 diff --git a/docker-compose.yml b/compose.yml similarity index 100% rename from docker-compose.yml rename to compose.yml diff --git a/package.json b/package.json index 4640edb9..b6d6c10d 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,6 @@ "@trpc/client": "^11.0.0-rc.490", "@trpc/react-query": "^11.0.0-rc.490", "@trpc/server": "^11.0.0-rc.490", - "autoprefixer": "^10.4.19", - "client-only": "^0.0.1", "cmdk": "1.0.0", "country-flag-icons": "^1.5.12", "cva": "^1.0.0-beta.1", @@ -47,7 +45,6 @@ "lucide-react": "^0.396.0", "next": "^14.2.10", "next-intl": "^3.18.1", - "next-sitemap": "^4.2.3", "next-themes": "^0.3.0", "nuqs": "^1.17.4", "postgres": "^3.4.4", @@ -56,25 +53,30 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.53.0", "reading-time": "^1.5.0", - "server-only": "^0.0.1", "sharp": "^0.33.4", "superjson": "^2.2.1", - "tailwind-merge": "^2.5.2", "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "1.8.3", "@fluid-tailwind/tailwind-merge": "^0.0.2", + "@tailwindcss/container-queries": "^0.1.1", "@types/node": "^20.14.8", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.20", + "client-only": "^0.0.1", "drizzle-kit": "^0.24.1", "fluid-tailwind": "^1.0.3", "lefthook": "^1.7.14", + "next-sitemap": "^4.2.3", "postcss": "^8.4.38", + "server-only": "^0.0.1", + "tailwind-merge": "^2.5.3", "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.4", "tailwindcss-animate": "^1.0.7", + "tailwindcss-radix": "^3.0.5", "typescript": "^5.5.0" }, "packageManager": "bun@1.1.12" diff --git a/postcss.config.cjs b/postcss.config.js similarity index 52% rename from postcss.config.cjs rename to postcss.config.js index e305dd92..2ef30fcf 100644 --- a/postcss.config.cjs +++ b/postcss.config.js @@ -1,3 +1,4 @@ +/** @type {import('postcss-load-config').Config} */ const config = { plugins: { tailwindcss: {}, @@ -5,4 +6,4 @@ const config = { }, }; -module.exports = config; +export default config; diff --git a/src/components/layout/Main.tsx b/src/components/layout/Main.tsx index b034664e..3a9e635b 100644 --- a/src/components/layout/Main.tsx +++ b/src/components/layout/Main.tsx @@ -7,7 +7,7 @@ function Main({ return (
    {children} - + Close diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index c7daf187..a87c29a7 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef< (({ className, ...props }, ref) => ( {children} - + {close} diff --git a/src/components/ui/Tooltip.tsx b/src/components/ui/Tooltip.tsx index 87520f81..5b747fb2 100644 --- a/src/components/ui/Tooltip.tsx +++ b/src/components/ui/Tooltip.tsx @@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cx( - 'fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 animate-in overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md data-[state=closed]:animate-out', + 'fade-in-0 zoom-in-95 rdx-state-closed:fade-out-0 rdx-state-closed:zoom-out-95 rdx-side-bottom:slide-in-from-top-2 rdx-side-left:slide-in-from-right-2 rdx-side-right:slide-in-from-left-2 rdx-side-top:slide-in-from-bottom-2 z-50 animate-in rdx-state-closed:animate-out overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md', className, )} {...props} diff --git a/tailwind.config.ts b/tailwind.config.ts index 73a4ee5e..823e5d28 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,7 +1,9 @@ +import tailwindContainerQueries from '@tailwindcss/container-queries'; import tailwindFluid, { extract, screens, fontSize } from 'fluid-tailwind'; import tailwindScrollbar from 'tailwind-scrollbar'; import type { Config } from 'tailwindcss'; import tailwindAnimate from 'tailwindcss-animate'; +import tailwindRadix from 'tailwindcss-radix'; import { fontFamily } from 'tailwindcss/defaultTheme'; const config = { @@ -78,9 +80,11 @@ const config = { }, }, plugins: [ + tailwindContainerQueries, tailwindFluid, tailwindAnimate, tailwindScrollbar({ nocompatible: true }), + tailwindRadix({ variantPrefix: 'rdx' }), ], } satisfies Config; diff --git a/tsconfig.json b/tsconfig.json index 6cb01c30..f92227f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,11 +30,11 @@ } }, "include": [ - ".eslintrc.js", + ".eslintrc.cjs", + "postcss.config.js", "next-env.d.ts", "**/*.ts", "**/*.tsx", - "**/*.cjs", "**/*.js", ".next/types/**/*.ts" ], From 31973c3462ac910c42d5d4742b41144901e29b77 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:12:46 +0200 Subject: [PATCH 13/93] fix: formatting errors --- src/app/[locale]/(default)/news/(main)/page.tsx | 6 ------ src/components/ui/Combobox.tsx | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/app/[locale]/(default)/news/(main)/page.tsx b/src/app/[locale]/(default)/news/(main)/page.tsx index dfb6b166..0b20eccc 100644 --- a/src/app/[locale]/(default)/news/(main)/page.tsx +++ b/src/app/[locale]/(default)/news/(main)/page.tsx @@ -1,9 +1,3 @@ -import { articleMockData as articleData } from '@/mock-data/article'; -import { useTranslations } from 'next-intl'; -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import { createSearchParamsCache, parseAsInteger } from 'nuqs/server'; -import { Suspense } from 'react'; - import { PaginationCarousel } from '@/components/composites/PaginationCarousel'; import { CardGrid } from '@/components/news/CardGrid'; import { ItemGrid } from '@/components/news/ItemGrid'; diff --git a/src/components/ui/Combobox.tsx b/src/components/ui/Combobox.tsx index fff8634f..dd3fadf7 100644 --- a/src/components/ui/Combobox.tsx +++ b/src/components/ui/Combobox.tsx @@ -50,7 +50,8 @@ function Combobox({ + + + + ); +} diff --git a/src/app/[locale]/auth/sign-in/layout.tsx b/src/app/[locale]/auth/sign-in/layout.tsx deleted file mode 100644 index fb41c57c..00000000 --- a/src/app/[locale]/auth/sign-in/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Main } from '@/components/layout/Main'; -import { Card } from '@/components/ui/Card'; -import { setRequestLocale } from 'next-intl/server'; - -type SignInLayoutProps = { - children: React.ReactNode; - params: Promise<{ locale: string }>; -}; - -export default async function SignInLayout({ - children, - params, -}: SignInLayoutProps) { - const { locale } = await params; - setRequestLocale(locale); - return ( -
    - {children} -
    - ); -} diff --git a/src/app/[locale]/auth/sign-in/page.tsx b/src/app/[locale]/auth/sign-in/page.tsx deleted file mode 100644 index bf4c4956..00000000 --- a/src/app/[locale]/auth/sign-in/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { FeideLogo } from '@/components/assets/logos/FeideLogo'; -import { LogoLink } from '@/components/layout/LogoLink'; -import { Button } from '@/components/ui/Button'; -import { - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/Card'; -import { Separator } from '@/components/ui/Separator'; -import { FingerprintIcon } from 'lucide-react'; -import { useTranslations } from 'next-intl'; -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; - -export async function generateMetadata({ - params: { locale }, -}: { - params: { locale: string }; -}) { - const t = await getTranslations({ locale, namespace: 'layout' }); - - return { - title: t('signIn'), - }; -} - -export default function SignInPage({ - params: { locale }, -}: { - params: { locale: string }; -}) { - unstable_setRequestLocale(locale); - const t = useTranslations('signIn'); - return ( - <> - - - {t('welcome')} - - {t('description')} - - - -
    - -

    {t('signInWith')}

    -
    - - -
    - - ); -} diff --git a/src/app/[locale]/auth/success/page.tsx b/src/app/[locale]/auth/success/page.tsx new file mode 100644 index 00000000..09e20f22 --- /dev/null +++ b/src/app/[locale]/auth/success/page.tsx @@ -0,0 +1,34 @@ +import { SuccessParticles } from '@/components/auth/SuccessParticles'; +import { Button } from '@/components/ui/Button'; +import { Separator } from '@/components/ui/Separator'; +import { Link } from '@/lib/locale/navigation'; +import { getTranslations, setRequestLocale } from 'next-intl/server'; + +export default async function SuccessPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + const t = await getTranslations('auth'); + return ( +
    +
    +

    {t('success')}

    +

    + { + 'you are now a member of Hackerspace. Now you can finally start praying to our one true leader' + } +

    +
    + +
    + +
    + +
    + ); +} diff --git a/src/app/[locale]/auth/template.tsx b/src/app/[locale]/auth/template.tsx new file mode 100644 index 00000000..216ea701 --- /dev/null +++ b/src/app/[locale]/auth/template.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { type HTMLMotionProps, m } from 'framer-motion'; + +export default function AuthTemplate({ + children, +}: { + children: React.ReactNode; +}) { + return ( + )} + > + {children} + + ); +} diff --git a/src/components/auth/PendingBar.tsx b/src/components/auth/PendingBar.tsx new file mode 100644 index 00000000..6150615e --- /dev/null +++ b/src/components/auth/PendingBar.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { type HTMLMotionProps, m } from 'framer-motion'; +import { createContext, useContext, useEffect, useState } from 'react'; + +type PendingContextType = { + isPending: boolean; + setPending: (pending: boolean) => void; +}; + +const PendingContext = createContext(undefined); + +type PendingProviderProps = { + children: React.ReactNode; +}; + +function PendingProvider({ children }: PendingProviderProps) { + const [isPending, setPending] = useState(false); + + return ( + + {children} + + ); +} + +const usePending = () => { + const context = useContext(PendingContext); + if (!context) { + throw new Error('usePending must be used within a PendingProvider'); + } + return context; +}; + +function PendingBar() { + const { isPending } = usePending(); + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (isPending) { + setVisible(true); + } else { + const timeout = setTimeout(() => setVisible(false), 300); + return () => clearTimeout(timeout); + } + }, [isPending]); + + if (!visible) return null; + + return ( +
    + )} + /> +
    + ); +} + +export { PendingBar, PendingProvider, usePending }; diff --git a/src/components/auth/SuccessParticles.tsx b/src/components/auth/SuccessParticles.tsx new file mode 100644 index 00000000..0f07aa41 --- /dev/null +++ b/src/components/auth/SuccessParticles.tsx @@ -0,0 +1,196 @@ +'use client'; + +import type { ISourceOptions } from '@tsparticles/engine'; +import Particles, { initParticlesEngine } from '@tsparticles/react'; +import { useEffect, useMemo, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { loadFull } from 'tsparticles'; + +function SuccessParticles() { + const [init, setInit] = useState(false); + const [mainElement, setMainElement] = useState(null); + + useEffect(() => { + initParticlesEngine(async (engine) => { + await loadFull(engine); + }) + .then(() => { + setInit(true); + }) + .catch((error) => { + console.error('Error initializing particles engine:', error); + }); + + const main = document.querySelector('main'); + setMainElement(main); + }, []); + + const options: ISourceOptions = useMemo( + () => ({ + detectRetina: true, + emitters: { + direction: 'top', + life: { + count: 0, + duration: 0.1, + delay: 0.1, + }, + rate: { + delay: 0.15, + quantity: 1, + }, + size: { + width: 100, + height: 0, + }, + position: { + y: 100, + x: 50, + }, + }, + particles: { + number: { + value: 0, + }, + destroy: { + bounds: { + top: 30, + }, + mode: 'split', + split: { + count: 1, + factor: { + value: 0.333333, + }, + rate: { + value: 100, + }, + particles: { + stroke: { + width: 0, + }, + color: { + value: ['#ff595e', '#ffca3a', '#8ac926', '#1982c4', '#6a4c93'], + }, + number: { + value: 0, + }, + collisions: { + enable: false, + }, + destroy: { + bounds: { + top: 0, + }, + }, + opacity: { + value: { + min: 0.1, + max: 1, + }, + animation: { + enable: true, + speed: 0.7, + sync: false, + startValue: 'max', + destroy: 'min', + }, + }, + shape: { + type: 'circle', + }, + size: { + value: 2, + animation: { + enable: false, + }, + }, + life: { + count: 1, + duration: { + value: { + min: 1, + max: 2, + }, + }, + }, + move: { + enable: true, + gravity: { + enable: true, + acceleration: 9.81, + inverse: false, + }, + decay: 0.1, + speed: { + min: 10, + max: 25, + }, + direction: 'outside', + outModes: 'destroy', + }, + }, + }, + }, + life: { + count: 1, + }, + shape: { + type: 'line', + }, + size: { + value: { + min: 0.1, + max: 50, + }, + animation: { + enable: true, + sync: true, + speed: 90, + startValue: 'max', + destroy: 'min', + }, + }, + stroke: { + color: { + value: '#ffffff', + }, + width: 1, + }, + rotate: { + enable: true, + path: true, + }, + move: { + enable: true, + gravity: { + acceleration: 15, + enable: true, + inverse: true, + maxSpeed: 100, + }, + speed: { + min: 10, + max: 20, + }, + outModes: { + default: 'destroy', + top: 'none', + }, + trail: { + fill: { color: '#000000' }, + enable: true, + length: 10, + }, + }, + }, + }), + [], + ); + + const particles = init ? : null; + + return mainElement && createPortal(particles, mainElement); +} + +export { SuccessParticles }; diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 0ae6abeb..76afd32e 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -18,7 +18,6 @@ function Header() { news: t('news'), events: t('events'), about: t('about'), - close: useTranslations('ui')('close'), }} /> diff --git a/src/components/layout/header/MobileSheet.tsx b/src/components/layout/header/MobileSheet.tsx index 9bcd6daa..bc4c2e6f 100644 --- a/src/components/layout/header/MobileSheet.tsx +++ b/src/components/layout/header/MobileSheet.tsx @@ -20,7 +20,6 @@ type MobileSheetProps = { news: string; events: string; about: string; - close: string; }; }; @@ -38,7 +37,7 @@ function MobileSheet({ className, t }: MobileSheetProps) { - + setOpen(false)} /> diff --git a/src/components/ui/Sheet.tsx b/src/components/ui/Sheet.tsx index 42742f31..18431ec3 100644 --- a/src/components/ui/Sheet.tsx +++ b/src/components/ui/Sheet.tsx @@ -3,6 +3,7 @@ import { type VariantProps, cva, cx } from '@/lib/utils'; import * as SheetPrimitive from '@radix-ui/react-dialog'; import { XIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { forwardRef } from 'react'; const Sheet = SheetPrimitive.Root; @@ -45,31 +46,30 @@ const sheetVariants = cva({ }, }); -interface SheetContentProps - extends React.ComponentPropsWithoutRef, - VariantProps { - close: string; -} - const SheetContent = forwardRef< React.ComponentRef, - SheetContentProps ->(({ side = 'right', className, children, close, ...props }, ref) => ( - - - - {children} - - - {close} - - - -)); + React.ComponentPropsWithoutRef & + VariantProps +>(({ side = 'right', className, children, ...props }, ref) => { + const t = useTranslations('ui'); + return ( + + + + {children} + + + {t('close')} + + + + ); +}); + SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 7c46cdfd..17bc19a6 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -18,9 +18,9 @@ const routing = defineRouting({ }, pathnames: { '/': '/', - '/auth/sign-in': { - en: '/auth/sign-in', - no: '/autentisering/logg-inn', + '/auth': { + en: '/auth', + no: '/autentisering', }, '/auth/create-account': { en: '/auth/create-account', @@ -30,6 +30,10 @@ const routing = defineRouting({ en: '/auth/forgot-password', no: '/autentisering/glemt-passord', }, + '/auth/success': { + en: '/auth/success', + no: '/autentisering/success', + }, '/events': { en: '/events', no: '/arrangementer', diff --git a/tailwind.config.ts b/tailwind.config.ts index 712a0197..f4720678 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,3 @@ -import tailwindContainerQueries from '@tailwindcss/container-queries'; import tailwindFluid, { extract, screens, fontSize } from 'fluid-tailwind'; import tailwindScrollbar from 'tailwind-scrollbar'; import type { Config } from 'tailwindcss'; From 5561c9104efa592b052330a325577a0a06e90010 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:38:07 +0100 Subject: [PATCH 22/93] fix: badge not being hoverable --- src/components/storage/ShoppingCartLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/storage/ShoppingCartLink.tsx b/src/components/storage/ShoppingCartLink.tsx index 6bd6efda..8d5feb75 100644 --- a/src/components/storage/ShoppingCartLink.tsx +++ b/src/components/storage/ShoppingCartLink.tsx @@ -36,7 +36,7 @@ function ShoppingCartLink({ t }: ShoppingCartLinkProps) { {!isLoading && cart && cart.length > 0 && ( {cart.length} From a34397cc9e42ec2cc55ff5398a8b2d0f2460843b Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:41:17 +0100 Subject: [PATCH 23/93] feat: add account signin form --- bun.lockb | Bin 266716 -> 266716 bytes messages/en.json | 30 ++++- messages/no.json | 30 ++++- src/app/[locale]/auth/account/page.tsx | 12 ++ src/app/[locale]/auth/create-account/page.tsx | 32 +++++ src/app/[locale]/auth/page.tsx | 14 ++- src/components/auth/AccountSignInForm.tsx | 111 ++++++++++++++++++ src/components/composites/PasswordInput.tsx | 48 ++++++++ src/components/layout/header/ProfileMenu.tsx | 5 +- src/lib/locale/index.ts | 4 + src/validations/auth/accountSignInSchema.ts | 25 ++++ 11 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 src/app/[locale]/auth/account/page.tsx create mode 100644 src/app/[locale]/auth/create-account/page.tsx create mode 100644 src/components/auth/AccountSignInForm.tsx create mode 100644 src/components/composites/PasswordInput.tsx create mode 100644 src/validations/auth/accountSignInSchema.ts diff --git a/bun.lockb b/bun.lockb index 598e05a5acbb82de0a61c1e3616e2e66a699f0b5..03621b425c3cd2492d6780599655e89e12c37838 100755 GIT binary patch delta 31 ncmcb!Sm4fLfrb{wEldmh*cs!D^-T2)+E?^3ZC}yHY|;z>$A}Bb delta 31 jcmcb!Sm4fLfrb{wEldmh*qIo>pnXLj)Akj8%qGnMwNDCo diff --git a/messages/en.json b/messages/en.json index ba8261a8..003813a2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -21,7 +21,9 @@ "selectMonth": "Select month", "selectYear": "Select year", "pickDate": "Pick a date", - "dateFormat": "dd/MM/yyyy" + "dateFormat": "dd/MM/yyyy", + "hidePassword": "Hide password", + "showPassword": "Show password" }, "error": { "notFound": "404 - Page not found", @@ -37,7 +39,31 @@ "signInWith": "Sign in with", "hackerspaceAccount": "Hackerspace Account", "success": "Success!", - "home": "Home" + "home": "Home", + "signIn": "Sign in", + "useYourAccount": "Use your account", + "forgotPassword": "Forgot password?", + "submit": "Submit", + "form": { + "username": { + "label": "Username", + "required": "Username is required", + "minLength": "Username must be at least 5 characters", + "maxLength": "Username must be less than 8 characters", + "invalid": "Username must contain only letters" + }, + "password": { + "label": "Password", + "required": "Password is required", + "minLength": "Password must be at least 8 characters", + "maxLength": "Password must be less than 50 characters", + "uppercase": "Password must contain at least one uppercase letter", + "specialChar": "Password must contain at least one special character", + "confirmLabel": "Confirm password", + "mismatch": "Passwords do not match", + "weak": "Password is too weak" + } + } }, "layout": { "hackerspaceHome": "Hackerspace homepage", diff --git a/messages/no.json b/messages/no.json index f52f7ba2..b3592f51 100644 --- a/messages/no.json +++ b/messages/no.json @@ -21,7 +21,9 @@ "selectMonth": "Velg måned", "selectYear": "Velg år", "pickDate": "Velg en dato", - "dateFormat": "dd.MM.yyyy" + "dateFormat": "dd.MM.yyyy", + "hidePassword": "Gjem passord", + "showPassword": "Vis passord" }, "error": { "notFound": "404 - Siden ble ikke funnet", @@ -37,7 +39,31 @@ "signInWith": "Logg inn med", "hackerspaceAccount": "Hackerspace-konto", "success": "Suksess!", - "home": "Hjem" + "home": "Hjem", + "signIn": "Logg inn", + "useYourAccount": "Bruk din konto", + "forgotPassword": "Glemt passord?", + "submit": "Send", + "form": { + "username": { + "label": "Brukernavn", + "required": "Brukernavn er påkrevd", + "minLength": "Brukernavn må være minst 5 tegn", + "maxLength": "Brukernavn må være mindre enn 8 tegn", + "invalid": "Brukernavn må kun inneholde bokstaver" + }, + "password": { + "label": "Passord", + "required": "Passord er påkrevd", + "minLength": "Passord må være minst 8 tegn", + "maxLength": "Passord må være mindre enn 50 tegn", + "uppercase": "Passord må inneholde minst én stor bokstav", + "specialChar": "Passord må inneholde minst ett spesialtegn", + "confirmLabel": "Bekreft passord", + "mismatch": "Passordene stemmer ikke overens", + "weak": "Passordet er for svakt" + } + } }, "layout": { "hackerspaceHome": "Hackerspace hjemmeside", diff --git a/src/app/[locale]/auth/account/page.tsx b/src/app/[locale]/auth/account/page.tsx new file mode 100644 index 00000000..30725ec8 --- /dev/null +++ b/src/app/[locale]/auth/account/page.tsx @@ -0,0 +1,12 @@ +import { AccountSignInForm } from '@/components/auth/AccountSignInForm'; +import { setRequestLocale } from 'next-intl/server'; + +export default async function AccountPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + return ; +} diff --git a/src/app/[locale]/auth/create-account/page.tsx b/src/app/[locale]/auth/create-account/page.tsx new file mode 100644 index 00000000..dbe8e683 --- /dev/null +++ b/src/app/[locale]/auth/create-account/page.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/Button'; +import { Separator } from '@/components/ui/Separator'; +import { Link } from '@/lib/locale/navigation'; +import { getTranslations, setRequestLocale } from 'next-intl/server'; + +export default async function CreateAccountPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + const t = await getTranslations('auth'); + return ( +
    +
    +

    {t('success')}

    +

    + { + 'you are now a member of Hackerspace. Now you can finally start praying to our one true leader' + } +

    +
    + +
    + +
    +
    + ); +} diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index 948b62e5..e64c7e31 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -1,6 +1,7 @@ import { FeideLogo } from '@/components/assets/logos/FeideLogo'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; +import { Link } from '@/lib/locale/navigation'; import { FingerprintIcon } from 'lucide-react'; import { getTranslations, setRequestLocale } from 'next-intl/server'; @@ -21,12 +22,17 @@ export default async function SignInPage({

    {t('signInWith')}

    - -
    diff --git a/src/components/auth/AccountSignInForm.tsx b/src/components/auth/AccountSignInForm.tsx new file mode 100644 index 00000000..085df6e8 --- /dev/null +++ b/src/components/auth/AccountSignInForm.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { accountSignInSchema } from '@/validations/auth/accountSignInSchema'; +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; + +import { api } from '@/lib/api/client'; +import { Link } from '@/lib/locale/navigation'; +import { useRouter } from '@/lib/locale/navigation'; + +import { usePending } from '@/components/auth/PendingBar'; +import { PasswordInput } from '@/components/composites/PasswordInput'; +import { Button } from '@/components/ui/Button'; +import { + Form, + FormControl, + FormItem, + FormLabel, + FormMessage, + useForm, +} from '@/components/ui/Form'; +import { Input } from '@/components/ui/Input'; + +function AccountSignInForm() { + const router = useRouter(); + const t = useTranslations('auth'); + const [accountCreated, setAccountCreated] = useState(false); + const formSchema = accountSignInSchema(t as (key: string) => string, false); + const { isPending, setPending } = usePending(); + + const form = useForm(formSchema, { + defaultValues: { + username: '', + password: '', + }, + onSubmit: () => { + if (!accountCreated) { + router.push('/auth/create-account'); + } + router.push('/'); + }, + }); + + return ( +
    +
    +

    {t('signIn')}

    +

    {t('useYourAccount')}

    +
    +
    + + {(field) => ( + + {t('form.username.label')} + + field.handleChange(e.target.value)} + onBlur={field.handleBlur} + /> + + + + )} + + + {(field) => ( + +
    + {t('form.password.label')} + +
    + + field.handleChange(e.target.value)} + onBlur={field.handleBlur} + /> + + +
    + )} +
    +
    + [state.canSubmit]}> + {([canSubmit]) => ( + + )} + +
    +
    +
    + ); +} + +export { AccountSignInForm }; diff --git a/src/components/composites/PasswordInput.tsx b/src/components/composites/PasswordInput.tsx new file mode 100644 index 00000000..432a3ba2 --- /dev/null +++ b/src/components/composites/PasswordInput.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { EyeIcon, EyeOffIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import * as React from 'react'; + +import { cx } from '@/lib/utils'; + +import { Button } from '@/components/ui/Button'; +import { Input, type InputProps } from '@/components/ui/Input'; + +const PasswordInput = React.forwardRef( + ({ className, ...props }, ref) => { + const t = useTranslations('ui'); + const [showPassword, setShowPassword] = React.useState(false); + const disabled = + props.value === '' || props.value === undefined || props.disabled; + + return ( +
    + + +
    + ); + }, +); +PasswordInput.displayName = 'PasswordInput'; + +export { PasswordInput }; diff --git a/src/components/layout/header/ProfileMenu.tsx b/src/components/layout/header/ProfileMenu.tsx index 13c9b860..9504a666 100644 --- a/src/components/layout/header/ProfileMenu.tsx +++ b/src/components/layout/header/ProfileMenu.tsx @@ -7,6 +7,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/DropdownMenu'; +import { Link } from '@/lib/locale/navigation'; import { UserIcon } from 'lucide-react'; import * as React from 'react'; @@ -20,7 +21,9 @@ function ProfileMenu({ t }: { t: { profile: string; signIn: string } }) { - {t.signIn} + + {t.signIn} + ); diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 17bc19a6..ce6b211f 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -22,6 +22,10 @@ const routing = defineRouting({ en: '/auth', no: '/autentisering', }, + '/auth/account': { + en: '/auth/account', + no: '/autentisering/konto', + }, '/auth/create-account': { en: '/auth/create-account', no: '/autentisering/opprett-konto', diff --git a/src/validations/auth/accountSignInSchema.ts b/src/validations/auth/accountSignInSchema.ts new file mode 100644 index 00000000..d55ac683 --- /dev/null +++ b/src/validations/auth/accountSignInSchema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; + +function accountSignInSchema(t: (key: string) => string, isStrict = true) { + let passwordSchema = z.string().min(1, t('form.password.required')); + + if (isStrict) { + passwordSchema = passwordSchema + .min(8, t('form.password.minLength')) + .max(50, t('form.password.maxLength')) + .regex(/[A-Z]/, t('form.password.uppercase')) + .regex(/[^a-zA-Z0-9]/, t('form.password.specialChar')); + } + + return z.object({ + username: z + .string() + .min(1, t('form.username.required')) + .min(5, t('form.username.minLength')) + .max(8, t('form.username.maxLength')) + .regex(/^[a-z]+$/, t('form.username.invalid')), + password: passwordSchema, + }); +} + +export { accountSignInSchema }; From 1343259b2ced9ff3c4813f53052b337223cf9a56 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:13:44 +0100 Subject: [PATCH 24/93] feat: seperate out tables and add seeding --- bun.lockb | Bin 266716 -> 267084 bytes package.json | 2 + src/app/[locale]/auth/create-account/page.tsx | 21 +------ .../[locale]/auth/forgot-password/page.tsx | 15 +++++ src/server/db/seed.ts | 42 +++++++++++++ src/server/db/tables/index.ts | 4 +- src/server/db/tables/locales.ts | 20 ++++++ src/server/db/tables/office.ts | 6 +- src/server/db/tables/skills.ts | 57 ++++++++++++++++++ src/server/db/tables/users.ts | 51 ++-------------- 10 files changed, 152 insertions(+), 66 deletions(-) create mode 100644 src/app/[locale]/auth/forgot-password/page.tsx create mode 100644 src/server/db/seed.ts create mode 100644 src/server/db/tables/locales.ts create mode 100644 src/server/db/tables/skills.ts diff --git a/bun.lockb b/bun.lockb index 03621b425c3cd2492d6780599655e89e12c37838..f0406701e4759e4b624af25190babe3ab1bf7f7e 100755 GIT binary patch delta 38914 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

    {{HGHBOued(veey-lw_zq#cu!n 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 -

    -

    {t('success')}

    -

    - { - 'you are now a member of Hackerspace. Now you can finally start praying to our one true leader' - } -

    -
    - -
    - -
    + This page is for adding info to account after first login ); } diff --git a/src/app/[locale]/auth/forgot-password/page.tsx b/src/app/[locale]/auth/forgot-password/page.tsx new file mode 100644 index 00000000..66ffbd3a --- /dev/null +++ b/src/app/[locale]/auth/forgot-password/page.tsx @@ -0,0 +1,15 @@ +import { setRequestLocale } from 'next-intl/server'; + +export default async function CreateAccountPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + return ( +
    + forgot password page +
    + ); +} diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts new file mode 100644 index 00000000..8ce77282 --- /dev/null +++ b/src/server/db/seed.ts @@ -0,0 +1,42 @@ +import { locales, users } from '@/server/db/tables'; +import { fakerEN, fakerNB_NO, fakerSV } from '@faker-js/faker'; + +import { routing } from '@/lib/locale'; + +import { hashPassword } from '@/server/auth/password'; +import { db } from '@/server/db'; + +const fakerMap = { + en: fakerEN, + no: fakerNB_NO, + sv: fakerSV, +}; + +console.log('Deleting existing data...'); +await db.delete(users); +// eslint-disable-next-line drizzle/enforce-delete-with-where +await db.delete(locales); +console.log('Existing data deleted.'); + +console.log('Inserting locales...'); +const insertedLocales = await db + .insert(locales) + .values(routing.locales.map((locale) => ({ locale }))) + .returning(); +console.log('Locales inserted'); + +const user = { + firstName: 'Frank', + lastName: 'Sinatra', + birthDate: new Date('1915-12-12'), + phone: '+1234567890', + email: 'm@example.com', + emailVerifiedAt: new Date(), + passwordHash: await hashPassword('Password1!'), +}; + +console.log('Inserting user...'); +await db.insert(users).values(user); +console.log('User inserted'); + +process.exit(); diff --git a/src/server/db/tables/index.ts b/src/server/db/tables/index.ts index ea1dcf2d..06e02ad7 100644 --- a/src/server/db/tables/index.ts +++ b/src/server/db/tables/index.ts @@ -1,2 +1,4 @@ -export * from './users'; export * from './office'; +export * from './users'; +export * from './locales'; +export * from './skills'; diff --git a/src/server/db/tables/locales.ts b/src/server/db/tables/locales.ts new file mode 100644 index 00000000..315f6e9b --- /dev/null +++ b/src/server/db/tables/locales.ts @@ -0,0 +1,20 @@ +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; +import { index, pgTable, serial, varchar } from 'drizzle-orm/pg-core'; + +const locales = pgTable( + 'locales', + { + id: serial('id').primaryKey(), + locale: varchar('locale', { length: 2 }).notNull(), + }, + (table) => { + return { + localeIndex: index('locale_idx').on(table.locale), + }; + }, +); + +type SelectLocale = InferSelectModel; +type InsertLocale = InferInsertModel; + +export { locales, type SelectLocale, type InsertLocale }; diff --git a/src/server/db/tables/office.ts b/src/server/db/tables/office.ts index d1e51fd9..7de264d9 100644 --- a/src/server/db/tables/office.ts +++ b/src/server/db/tables/office.ts @@ -1,3 +1,4 @@ +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core'; const coffee = pgTable('coffee', { @@ -5,4 +6,7 @@ const coffee = pgTable('coffee', { createdAt: timestamp('created_at').defaultNow().notNull(), }); -export { coffee }; +type SelectCoffee = InferSelectModel; +type InsertCoffee = InferInsertModel; + +export { coffee, type SelectCoffee, type InsertCoffee }; diff --git a/src/server/db/tables/skills.ts b/src/server/db/tables/skills.ts new file mode 100644 index 00000000..dcac4063 --- /dev/null +++ b/src/server/db/tables/skills.ts @@ -0,0 +1,57 @@ +import { users } from '@/server/db/tables'; +import { relations } from 'drizzle-orm'; +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; +import { + integer, + pgTable, + primaryKey, + serial, + varchar, +} from 'drizzle-orm/pg-core'; + +const skills = pgTable('skills', { + id: serial('id').primaryKey(), + identifier: varchar('identifier', { length: 256 }).unique().notNull(), +}); + +const usersSkills = pgTable( + 'users_skills', + { + userId: integer('user_id') + .references(() => users.id) + .notNull(), + skillId: integer('skill_id') + .references(() => skills.id) + .notNull(), + }, + (t) => ({ + pk: primaryKey({ columns: [t.userId, t.skillId] }), + }), +); + +const skillsRelations = relations(skills, ({ many }) => ({ + usersSkills: many(usersSkills), +})); + +const usersSkillsRelations = relations(usersSkills, ({ one }) => ({ + skill: one(skills, { + fields: [usersSkills.skillId], + references: [skills.id], + }), + user: one(users, { + fields: [usersSkills.userId], + references: [users.id], + }), +})); + +type SelectSkill = InferSelectModel; +type InsertSkill = InferInsertModel; + +export { + skills, + skillsRelations, + usersSkills, + usersSkillsRelations, + type SelectSkill, + type InsertSkill, +}; diff --git a/src/server/db/tables/users.ts b/src/server/db/tables/users.ts index 72186a02..e5a0854c 100644 --- a/src/server/db/tables/users.ts +++ b/src/server/db/tables/users.ts @@ -1,8 +1,9 @@ +import { usersSkills } from '@/server/db/tables'; import { relations } from 'drizzle-orm'; +import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; import { integer, pgTable, - primaryKey, serial, text, timestamp, @@ -17,7 +18,7 @@ const users = pgTable('users', { }); const usersRelations = relations(users, ({ many }) => ({ - usersHasSkills: many(usersHasSkills), + usersSkills: many(usersSkills), })); const sessions = pgTable('session', { @@ -31,47 +32,7 @@ const sessions = pgTable('session', { }).notNull(), }); -const skills = pgTable('skills', { - id: serial('id').primaryKey(), - identifier: varchar('identifier', { length: 256 }).unique().notNull(), -}); - -const skillsRelations = relations(skills, ({ many }) => ({ - usersHasSkills: many(usersHasSkills), -})); - -const usersHasSkills = pgTable( - 'users_has_skills', - { - userId: integer('user_id') - .references(() => users.id) - .notNull(), - skillId: integer('skill_id') - .references(() => skills.id) - .notNull(), - }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.skillId] }), - }), -); - -const usersHasSkillsRelations = relations(usersHasSkills, ({ one }) => ({ - group: one(skills, { - fields: [usersHasSkills.skillId], - references: [skills.id], - }), - user: one(users, { - fields: [usersHasSkills.userId], - references: [users.id], - }), -})); +type SelectUser = InferSelectModel; +type InsertUser = InferInsertModel; -export { - users, - usersRelations, - sessions, - skills, - skillsRelations, - usersHasSkills, - usersHasSkillsRelations, -}; +export { users, usersRelations, sessions, type SelectUser, type InsertUser }; From 5351dd3ab015c5088f9044a72167acd142a9c623 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:22:49 +0100 Subject: [PATCH 25/93] feat: setup password and session auth methods --- bun.lockb | Bin 267084 -> 268556 bytes package.json | 2 + src/server/auth/password.ts | 53 +++++++++++++++++++++++++++ src/server/auth/session.ts | 67 ++++++++++++++++++++++++++++++++++ src/server/auth/user.ts | 33 ----------------- src/server/db/seed.ts | 16 +++----- src/server/db/tables/users.ts | 12 +++++- 7 files changed, 139 insertions(+), 44 deletions(-) create mode 100644 src/server/auth/password.ts create mode 100644 src/server/auth/session.ts delete mode 100644 src/server/auth/user.ts diff --git a/bun.lockb b/bun.lockb index f0406701e4759e4b624af25190babe3ab1bf7f7e..cdd4f75c66a942f01518667e18ee798c7c6febad 100755 GIT binary patch delta 47384 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 Jy+erQ2mr9%#cluq delta 46540 zcmeIb2Ut|syEQ&@V3fhAUs1H+`|P+}H~3Wdy?Go>eDzLkTT}^-Oie77 z^o4`TWHp(R2S!Im4T9HMSbPAzvqk5RLZ|-;v&mEdnAP6!56f;c<%NG680P>EN{Ef= zj`;RM&jq~^&;{rX%nzgyoJq+qob?0>K`RK4dWeV-cmm4+M<6%FfCChhDId^3o5|z} zbb!A*@Bwr;U^S#t9C!&jGkOG=2e=040h|V8xV}Kzg(5d4Oi8BX(jb`g)<9;!gc36Y zcM-ZAuoRF1H@fO}dnG>y=mkCzSQywz+SQS01v25s2*-?Gk$4cubQS?!li=u9$YiPn z^aYj&<^(d~ZxM?4i9{7x7J6Ta^(A^p{2A5244nm*0j`laR$?TOC9el83iJXdF^4(f zK$^+VkS*eMAmU6uBylYeCdsK1hXe7K93?Rvh{7h9DP=Mh0XhR&V~VVxS{P&8>F zSTs_-D2qrupdI?}Q=!~#8bgdqMOehq{`nfLkic&uY zq`eD}_Rr88*rUDxvY`$DnUOU>W;`J}ZbkB$xtf>EXSB&gxyqyh(up zdQ1~#3l2_*t`yU6Q0%%uy-UmnvgoC1>s9j<$f7v|S!G8Mo<;r=$drf67LNlmr|o4M zM-7Q&6Pu!96B3EFz@y70B}c{e=u5(w4zf-Y>+0!Mv9`WGdX0H3>-6&Gv|yhVjA_G| zM&6umFnt))hB5CL6QMExl+|;Z>b+uz^hgiWd)X2oyZ`29dV)(NrUF^D6M)RX2q3FIDn7n% zOti_Af^bYIZeUzY9Q0c)^rk!mbOE0puE)0u83>Lq=T>^eZGenu@SvDj zHc{TzdcYY#j-zjYIe@+5`}d%KrwBb`y(1IihxRj>c1ZmYkiBrd#Gdg3D`BZy5z$~wTfNGj1DTO{Am^p8f%&Mzj-9bhd%cP+&~rh5jEv?7ek(H`)30~*z?eY^ z(@~(j;D<|0M9nZhqsBIOMEx@Zz2V^k>;z<$4(`_@CMw2cT7))Z0N>8K{{-j-q2Gtj zf&Dp<8M1cOGxDU1p7CoEjlT6MbY^f9koG6K>G73L!l+;Z`GD+H?T`T5ydIE-GrQ}< zYbcO>XCOOQ7?2tEQuH;-1;~v3j)BjFZUYMgOQ8K(b0dKqrrW?XUIFCFkrX>PDkc(b z7oQLt&o;F5(%bf0Z`~jswU85ranW%-6X5?dbT(B4kQp%M)DY;bxjhJ147dQu8cLG! zL;{OIw*gB6ivVGll>8bAGQgKW1~?34&R0sD1Z2RzK$ajJ$QpRwQ_pC-0eVLLfsDr; z$ar4&*ZHr248IS^gck#8H%dc{|1NMaKwTgcE+sJ=kO6-{y)fbPKqfrFtT$Qnk$UF} z9HH0H8tCk_HXxgF+9+NB8OWxoAnliq)|;{;!sShZ$FIY6VmTDHN#0~VKpq&dEiVnz z6Lv=g>?<9{m`qrplNIQU_#y1rrneA}C-BKwJ>yq_>@#QJ&jNbM_>RJk866JPwhtlj zV8s0==m~9<29ZdZZQ2k1tTNB>I);L0!h)1b;(k{O7w954>Z9Qpz|Y}|n?i4oyoM9hT01~OcUp?ceo zouzligh2!Q4URLJ`ax&-Ut!0Z+XiF})d#Y`BWLSPpeBJ}+jIuH0X>0UKm|y{8%Tiq zULft}0=YGuE%YW!fX-gsLShvl3$S;-UZC92IRm~JqR0O=kbUX|knxAET&P#=L?9Db zgn-QP-9>tUi$F$@2%ep;o%HtuGNFP}-!epPnX^pNxHP@s{bL4VtCwJkLyPj&eYrl- z4(c7fGJ|SZ5b-1>_m3RZyHeliVWnk&?W^?pDlVp9O#jG)1W)jszhYMF0rvyh7xPNK?HWDd zP#`l_f30p`0LVr5k;HG{&#{rTPVWhWfI0XyvKj`=@jnIZut85)4`>e;gPO1+d`nsl-Z4#T&fauQ2)8SxFlYlJQtz~+EXIpd~`@rag zD3j?Nc={WA!@bb8mIg8d!y@BiO?YjC&iI~g(`#e}bcS27UFZ9=J@J|dJ&B!uJ`7j` zQ>Eb;AR{QUQ;)d&z{n_c4bvRxfIc#Y0t>)5O6p-!uPODCQg;RBf!(Y9dbl5eY@SO%wtI1;##)Pu>|ZIqdmmHI zBYN!=khypWy&(LfKGh1Fl)M=Z)=E<#J)#r34~~iLVXA&y*NXs|F(-*nkLmhNiN}FV zcsr2Iyj0>;i9;nuNo+1LKtqfSFG=JBvLt4SkB;j44T&d#EP1-bl@e8nS5Y!%-~^Bb zdar^c!SFv*lK);o3a~_3OVatQdNEI#q=uLEepn004pbb-xuw<>y#+8_BV!|bL|1~& zImsQ!3K(`(5BCL-;V`lX4eK9mdWi1HffZIqA4s2H)BWA9>+zm~&H*O*sAR+AECO?Tu7_@D#5@tUEgmGg41>SQv7zSDO&d>NJjk?o9^7S?=P4JjXF8I6ju zsbS>;l<%$Tsq!|9J8H6l8dfP#4XF@hUa3yOyH!1j_xh@}Vvu>18d5RH`Z?;~hN#Wz zg$jNaSF~OqHD`qY%R;!iXs%!3YNWY>(1va3vd)341wtt-rsXcQX6hNQ0849Bb~jom zJ94R^>tMoat+{5y)kZV_0j_SED;DFSyB6@Qi|Sm#X32v^wJE*KUDPR6 zgDkmFZ*l7MssWY}aJAB0XW^==xpJdd>AoZ3iqW{ca7AdYS{VI$i0K)wuiy&Te7(`o zVVY}nhU+u9LNqQPxhuZ$=Cx`_V36{ljG7W?vzG8O znd)d=&$X7n6O>>mRyDPPpLG{Be`t=XYXv{$k(ZiM+h#7Mo~#{Y?Nk=y4_=CT!OPFO zUuq7jYeg8ARa0y>a~<`hEy!}YJoA0V7GSMZ0WJi|t)_PMcZ3o_+Hwu9R+_6~MZ=W@ zSGdNVhpV~fDq4vZh7jgHs&_kfo2e$<$7>ISd!txL$yzoR1OLV7NMJ+t zTAdOcq%A}g=sWm~4HBF`(tU%>A=pi;sFs8_QS{-o541MWOzMRIKWpwFqzTQe&CyNNDPck8b?QmHTU2Ya zAakr5(k#fjv98h2FvVGKK?~Ev1p8Ubqv-5|S`5|>&|2tPBR}goXpJ=Og3Zs8zrHqQ zqI`W-Yl|S{JETRBHAe$I-+FsRKj(S_fH5gfL84>i}g!h#K13Wb_L8(==DTH6Jgoz;+bLDtabCKGxBnh%}AIsqD|4K!_8Kl4%5+CIo?M*XuZYZm4j z>Xi0DmY>M6Y}S(DC<}bGNx~8jEtn;>o`4JaPquLKu;f91X{>d&IMv!Q$hr?Yn;fAk z_?Zi+Q#uA=0yv5H?W(m?5T>J$PC?eit&QdmujB6o1znAWK~Ja{Vf2yBptpS_f6Z?SmG9dAJ(VCCIu19Opul>e|sydC^8a)y0M)TDt~W zXJb}FjlmohYR%OS^NXe-Qfp5*S!lgtw?S*6o@pFlQQB*>Qr7@$f4Djs7T86c>=uM> zVT}y3^yt8GhsD>r0xo~ppt9@vS#Lr^ht{jaS`<@GhGy=rhI9`yf2>aF9%OkAA8jvU zt&RlHvt$lOLSxgRnacTLEKG?CQl4~DPes`*jXHBJKNA&TodlPj@e9Fz7N;)Aq_%D7 z0T=o%Cf0&oxt?HJu!O^f-rh6NQFnm)5iWFgxN2eqp|4`QG6pX6d{(!YpZTgfrDu@U zFA^PI%gco>e&!*nwO5d3J2=d;2;tCO?-~koYS{oy+xS?NC~0A72^Z#3q_YApo96l& zE(}9t(YFV4!6qFG7i$1934bRjY$7dMt8cX4cl1Q&LDLg?1XrlGE%NUv6S2;Q3+n=^ z9#fs=7&L4`YX?~K^}_xHJo;5fKkLuEvA7wUGBQRz6>C!t$EeP6Hsw`}8Vd01qox27 z`>3blY?kysoKerj1y~=$g{qSgHSdcxBtzQ(EhAd1Io4Qhm}Dnt$gd1{2%0^XVsV+Z zBxv?HZ$d){l;LXh%cNOmKSGyuL(E#gLE|EU*(Aiz5;{s- z0EYx9D@Lg)Lu{5DquG%;DYb_Sb2u9s8}i-IT9UUoCxNF+X_2I!8fsJaC8^GdHcR|PE8q(1jnhThTE)x?jW)k*YS~RYaewI{d?KF$~a7C+Ukez0e zRp-$*%ecun@X)+&!G%rR@BnMT6n)CIaz?lIg@zHyI!9Dzp<%0md^o2VIqCve3w8S7 z0Lv!0uoA-eFYEDy~$JngXr|Df^hK{vaN6av~2ddG!6I!5QVtE1$3&N^Erw{dBqit}ksaB(ncHE;D@sMn_64hI*iDN}9Mf{W0N3=_)$XecNm zc?FkVsAh|ei08l+qb2qlF5Rx{5-lqz)M2=^O0t$*s*fi<*%8q6w#7c%k`66Y4GRoV zax7C*X4n*uH1*UBn^mMCJ>&y>)NX#(d(eELIjZ4te%2z(O{R*_&vFwJE5U4 zv7|$zCAwf)Kg*BwQ^UFhSSx&lKBUFK=cG7jp;{PhKa}Gism`f3Yn>H(8>4@B@v|<0 z#%Q%?F6&LHW!JVc^;hbv4|?7Pf2RzEJLA>RSjEU2=9%B1VP}tdEO3>+b8z6As|;GD zhN?DY?hQdmX3q3P+kViUI<8e`7R4Wjk7)bw-;Z7{kpOV}G4OM%UCZ9nS|^%TgBn~Z6gDZGHjT7wDNH+Zv} zvcP8Dwb^9qsgr0L&n?KVnzL(w(r$|yy3l4_45AIZvuUkh-kQ0ln?tLv)yFu=BY&8@ zj&D^{7TK(?L2@QSHc{bCx9OQiKSEX}LaPctbcm{Wl844hLa#z|{t1oEhJ_~F-)TFZ z9iZga>Td-!8fyDh>sQe9NLcSKJM@8z$q1XDZqV?Aja6d_Tn)5behrsQrmLUTYp0$e zbl0+e)<|g85i=Gh?8=uy(_4+>;yyICjMg8lzPt2rde7-EHN=8C-MLE*O|x0Q1L=zZ z$ONH}Xv_q5G86o)m3Qlw7S@;&yIXbs$foSy zt%iPNvpj*HFQ3t@UVHQkLQQt`vm`>RqwV>Q!=*R&1>_;;$NEMVyGRV5rqC+u>EJ2NjGLR^Cj z3qL}XJIqYdH54xF=+_21!NJlaSE%2DNAwogOBe<%P%9xj#grpz$_AU_@TqzV5dW#_ zywPSo@Toqn>ODF8QJknDfYuF^jz`r~8*P>*$F;+yjRBSgaD}O7;QBtpRsDp)jmdDG zg$svJuya4D9nrznJ;Sv=!}W89tM)0wZeoV(T!zc-Gs8C$uEtu-YcgCvWw?A#8@{99 zYN+Pi6zHfqn3e2jNYT|a!<7nGeeFT+3%G*yl9%^WN}pAoci1fPXIb)`I|3~G;R@4S zInEibb{Vdf8Lp>rHPP&9pJ$#Kc4CI>T!zc-g5evP;aZpBdY0h|ylB`>fD2Fji0^cU zEAQt9*CE4|1{V%Fb_6vCZ20vOc1<2V?63XzclzV?zCutC!W3Pi&SJSF}wlo=rExC5JI4kGogY z(7iTm*{k~Y0i7<`PnmpGJ+;?n`4VIU^~_!zfm}0o2^h3W`)g{-KAZC4HT4wW_BGXc zzfCE1T@3|9UsqEA^RKI?02i;T&IfGPyf=_Ia)ix^%}?odLrnp>9wdhXj%=|qJiehi zAH??aCLfh@9t^Mq-{NkMuElVLYpzFdq4mMlzHPXs!G(SR?iyTZ>4O1EsV`OM!?ltS zJ5$hJLi^zZjAmj%^n8CpiCOA~;!=*kF$auy;vOy+6Xg5Xb9|9SE7K9m_&m&zL zEQDAgD#fh_0;(w|8C+`t0B(vtsgAXXt60V4{K z5oJbFfp{a~&zNw3kHg+XjU-}Zw>W@c@db6?`LnDTe1%~o13zBNWH?{V$ zf=RS)Cap3fiykidw;{b+N`E4&j-SjkTn8ZgSQjAvOkE}49mw>0NbG6G4GtRg;+sS| zd*h7}!~#8llYk63g`QcEcGJPLbE-h<0?3Rk0OHTIkZ%(60yjaY-8M721`T(Da0VWb z9!G%qGkuCT`X2{60nY*1(Jo8=n#7wxX5ee-e;>#Mew6-?fqW5JQ@;R7Jx{_rOZJkQ zMC!ju170xXi^v4b^pwa1EYN9^4af}UlKw>M1*HBiOnRFK0~V11Gb0l!DtRLHVp1nk zFAgMCQu0KWxU9rVK-yK-{gX5Yi7L{dx-_Vv8<^gPwD*<%L?#dbWDV5?(!ZX>`am{e zBdIqA@grIh(FVMi5r1z+MOgNCUFWr41&Yr z5|9yG2j&3Y1#-sv9Y{MTq|OLjfS^oyfJ`{A)bj&#K`#kp=k${PywXj(azMU_)XO`_ z@n1m_6{W%d1~Q|y98mwvsZA#Qzk%3BnG%s6lN^zRcUHwnAggq=#AJzMrT;h}tA47~ zrvdS2`Venyve{A>K*l!@$oLjW|E1D@x%B@?`X{ZF9&3QCi4D?Ylf*4R&Uon(_e=gD zkQq7w#GmOi$)AyU4oLqiKxXJFkQwDyRES^ee3I!KN!$bSMPvZ^9TyWX$M8j@^B0LP zfh^%~Qhx_Eon1Z2WFfiN|>Xo&XD3kUv81*JzJAOm^=8L+VAi%GtO z#8T3~wA8(TjJUkiD*`zo`AEGwkTn?q#Gfe$Z#g*rL*ZaTO~^`Q)wP07sx96aQD-1~ zZC@bmVjBtH(wVLe0gsX+Rf=HQJvTLk<7xCY1sHv!86FH3w4 zWI|@tBki4l%!pN@Gmsh217vtNU>TrI>J5OLN5X+zZ97^p{^2%tlLowp#21kf_X2VP z8UY0&tYo-4NAoY#Xj>vFZr2cnEyPVsl zAyH8~pD(D6KkuT|%5E@>dJlbopGeEhNPZu9Ms*N~4r2OL+GR%C9g{qfcBg=h|1-&F z!t$Ws^k4*Mq#=<3&l1(b7reNMGF_0qMCuoTq^?MwNd792v0az?4e3uLe@pVWCI5HG z&i)-cF<)?K34AZ{o{WIVh<}iJW@LsQfM+K+vqMN^eL6~=NIQ$vi5xw-rJg&R?8J5g zzHb=3zs`oqDG8jXN&m@&Si;LaEKpfg>3{PVCBC>=*66*n(f%*~~ z0Qn-4ZwUE^6PYBfO1r_3RoDZ~8Ut_5|KC#?*2KT3GH*Q;Vb#3tu;f3_s)e{_DNjoy3og%uU4AhUT22VPkU>b8eB^*xXi3ZUo{N60V|dFbMy~AXWy0 z$Sa>LZUsvLu@5@3Tsn9VG&6v zBK8o93fC||F%d&3E)Ee&2=``yl41a%lsHK!Es8e>lo7)SUg83wtSHw4P)>{?lo!_s z6@+&aiRd;U0>oAlYa&48ZwsQfh-?d@T^kTbNdyVkb|9SEf*9Nm zL|t)+#BLI$+k>bt2DAs!yB&y2BpQn19YDCX2Qi@oh{oaqi4!DhbOaG1#&iUc*a5^{ z5>146ClF;jf|%0@M3}fk;wp)Tok27gshvSg?gZi&65*n57ZCoPL9FZoqLp|;;vR{J zt{@^rT2~PBx`42B1JPE5cf(t7R}ec%v=`<`5HCqYM}p`mwvt%W4MhI#AUcc4?jYJl zf;dW|t8k41;nW?(;3yE0;t+}5Bue)H5hVun0MR=N#3d5ZqIfh2w;muSM1$xhE|54u zqDD^;F=9+l5Q)(s?vm&$ynBHt+Y`i`ULfMc9THbbH0%u`UZnO0F}W9rUq}oPbz?yI z_Xe>t21J5*LgF5Yh&~_&i?lu<=EZ=p^aU|gg!csz+y}%?62pW!7Q{;u(Xk*#h^-{n z^aYVW4#X%C83&?WEQq5dl7wqN5KeI*2KNIoMjRrsn?&h&5aYyvco4n&fw)9sf+*e} zgj+m_3H?D#5*J9EAW>rgh$&*s01%1&LEI%VRd^2sQFZ``IRimV7k5ZpCDAYe#D^j^ z0mS5iAbuf{D(Vgb;hz9vJq*Mp5-UaV5g^=#gP1S^#A4N0`Tgcu699EQn9URuXH*fXF`%#6A%@4n(`LAdZqaAY8|Ta2f|< z@OTi1#32&9NtB)d;)ob90YvZd=3L_P1aqkQs3<-WgxdsIOqd9Z)PL6n^YV$Ng`XT%*6S4lLS0^*!VodRO=WDviQxFG7LfbgFJVr2@5 z&&3lG_eexc1#wBFO$9M81%zc9h$|v|8i?SjAa;_tCd|`8yd)7l9mEZ>mBgB9Ao9-u zaZ5zb0MTwbh@&LF6s{kFaGC*P@P{Doh(jcHlPEnC#5ZEVOc1?41aXPPT~Rz0gxgFI z6H-BZFD{TcL88Vi5ckEHSs)TqLEI(rKzOSl%FY5YM+NbtxI^M9iH5U5{3KFmgP5#> z_=Ut1QFjgq|Jfi`&H?dEJRxz9M8sSWzlgNCAm+^hVc}x=LWBzt!E-_EB=M^-&jaz2 zMD#omzl*IT)(8;!=bM`-ugs$Rd~+MoZXTGUWd1aZJPW`$%?C4h0RoxDp#=!En?&h_ zARNSig;>rU#Yuui6ki0$CWaBRiwguNQEoB7D#j3;#Wg|>;k^X#ftW(bDee$*iJD6R zxkW0$McgO2in_~?r2kS_tz3pA^NJ@V?vaQ{1Cd{(rGc2Y41{Glh=L+~IYMIdKyVl4 zj{w*_5In?I0yYmT0EI;)0h=3y0}q!>WJ=7CUJ6kiR%=7HcP zE)cMJSOX|0#t^W1AXE_EYXR6i5Gsi~1Z*DG0jh{p0yYl>Z&7zW0GkIwHSvUi&BF#j z4UtB`=7HcR!Z!l^#acp5VcrC&CE61L#8yI}uxI7Fx? z+_wViivffN;v}J=D83EQNDL!178eM?qTF^sh!{f%71sz&g!c|qe%bA){5d;N`C;M? ziK`?U?gY_Xr0xVUc?XDJNQ8^JyFmEw1hH}#h*shWiF+g>(m_Osv~&>jc7d?$2GLf8 z@5WnjI*6Sl+6(g@5HCqY?*Y+KY$dT~H;DWngXk`2VH_=r?*rdc3?uXs7xtOcE!uyFPEUOZfARQx)c^mbH?@UV zS@mE!M=|oK*~wC^94>yOhktASM$Z*|E7I4h|D#-!sapE2^^n&P zvNAqn%o5U3^WAU*zNxqbo-0$lT4@n^RS|gG{D&p|BiyI@f&lnSy{I$z1g?sK8huEKdv;T z!#BHG3v%LnbF9+hD1E=vnPNYfo$O2tuY97^Lvu09Ieg(k%WMUPD`5POsHoVO%6$^_ z&r@q%NuTl1+`(-5{s!(qF$+i1w>&bR80ZEqnq>ki#WQ4p$Hb`>SB*(Ixgqttp4lgfz z&5|7N@v?9{;pZoW49>f|43`^{V%)_xF7EQKtP2cTa$e$PU|xOa4FkR!NRA)Q^J)rT z4JF6htdX+By!eYh{tGWtcW^AZap#xMs68YX!n^QHgjZM`L0W;KlH+ZVY?5mtIX7^B zB6qxu!%M;p%X_!HXUA8VM3DS@k!afAYsvIP_3UD(*CN@}d72&QaJLV9{RRUK+aziD@k+cZS$rrC( z6?~Fa=0|C3T~O?jC*u6>n)NoF`5AiR_I5b`7B z5#%xC352r^XP2KLzd)WtUP69_{08|Q@(1J<vsF1U_JQ<;aH<#x;Y`5m z6xkt85G%wP!uj$8h#8_l7NH^deImOFCZ@=ze0Y4{0`yDyd9aD4_RQrY{i?)b0Hr>c#WEuYHvb# zJ(eHbSAtZARDo1=5M>4^WnB3^j{=Z_5I2as=sZC2N{U7N{BFt=2-iM-MTlP?;+N2Z zA(4=Uh?W!YVhHEjrI2Nid607u-oE8;6$Cn?BCa$V&*%C97FsmqL9BDo~KMmo76KU77)3y@QgJ&=zfn<29y zymiUjxrHG`AVnd?gm;3{xTO&%zt%vZPN6#hWhn*WYPSlq0+I&dO2?Iry1|>c(Brjb zinyJilnCnzwF{&ZBm%-sNlOSDV>0a6Fzm_f)eew9kxdR@4rI>EoT2AI_+7s0=@EmJ zXE|JR!OdHY{7xnZxQiH^ta$l)z*z|54snBU{w@gNyqzDCc`oNZN*8f4St*}X4*q2! zUXU`7tl{Q@n+BN*VIN>$7!2WJF%dEWG7d5pG6s?i;eGTGkkOC?$SBBg$Uw*-$S}wN zNPq6RxRP+fiih-r@XL`=kZzE!5PpfWGlZj>qdN@J9>RXo6w(-CgVcraYn{H3W{}zt zu2!7++CVr8IZJT1s1FH%_;b%)0}j@RACQ5K8Zz89C08qxUp=^)C~cS!Z2}?w5FVpX$BY1bTR*k(nBGvZ~lFbv{P%BUhnVC0N}8L`1pkAN5n zkvB>}zqZoWuxCQ7F{3{iZBG9V;EjyrLi$>cjdseM>&#r{w(O1wA|ZyY(L{`#LY^EG zVS7hI4BMWW+`Z^#`?KRPU>{&_>5h>|o?Kt4^BckB;xhRgk!McG;5hs^>{zn~$LDav zoy4(hj7#DD7xtG!tzTA(^Xr2z1&G1#-j~@v)F>hB^?VW)WYKmMf$E zM!=Ja7!ez}G%`67entii8zXU}f9T^{&xDa_qx6Q2!TWOj8;X%rLpKsW^P`ikXrj239@vV12ebun*-z`rb1>xK7>q$FcOaEtmEFu40$8t zQ{c`#jniflB#HC3F=w;0jDj%Y%=5G{MRQv23h4~#2>B08#Yx&+Y*dkvLnC+p^O-~Y zbF8+6w1c#PWL+qX%&`hHx8YRiGcs}0;WmaTn~jnRj)j>CVXbCus(~E;S;-V|nQP*I zvKF-Y|9zHAqZTrEE@KT~Ny*QFu%pg`{8J}oZ6ZvPRxRvUTuxaN96H9B=2$i&H>%I* z(eJAI`3Unne4CEwa z0pwH25y(f7<&edYMUaJ%Wso$;639}iZvt+EY=&%rtbnY7tc0wGtY!PJhOB|CgD}8G z$QH;}$PUPM$N|W1$Uev}$X>`!$R`l;LtSPQJ3>EKvXQz2hK{~RJucK^IAZLR~aLRf1Ia}Dw(y|92aq2i_aV0+LL?P>ofHxzgr!#lr(92f?65zJfeY+DWrcN?QVpMC z)}EzQFgF!3vy|falyB-RWX1S7#2U*^lpb>_S4+j*1COy*q%>R{K&VQ_?=QOba37;O zTM@hh28yOyXy=0HWkZIq&_5|*?IG2rAqdFXj{Y++V3syAPhxi@_vo!Y*dR=O`>#?3UlG z>N~sp<~E4FD$D6(IxFtXR@}w0*-9a^?)K=6$9WpxTa%Le^5ty(6>}IE7cja3KE%Fr z34O_5m=^8WxGN8`&7G^b;iLbeOF*Kh zT?BZFu(^PGBKHkIm>4rx=~}ZJKC7zy))E;$e=X5-gefd8tXJ||^E&ARrI09oKq=tp zi zdEqSf&qFy(;>x_g#-i0xzHC+5s#>a*McjO);G1#a64s&lNUNK8a#pEkH43HIsOYpn zshHJEuPt$Vfzqg?VgA?lHhg5$R1=X4m98EJ`xg5*)0Zuy2YZiXG{b7*{9+|fmNFQP ztd+BxD0W(@`2N{=vwz7+jUP$&9FzCllI>0P`AEyz z-8^FEGITnbLm9o){*HXpE-XfDf4-u4Yf06A`+jz7DrR=+?ChnIQHpcd(g2W>?35t_%&z$7Nehx z89Ne7iqVE=#TXbUKf8%VA0ZRQ&p}TO;d#69!zn$wMjoGxki$t4~jsE6^(-J6q=?&Y!mG|z4j zB#N#_w%d!E>roR4A`Y6zIDDL*2Tjp&i&MMknDMds^o=H_!8sxg26_LwUThI(7|#~* z{d(-O77ObJM7KuN-hk+gpW8nCvf|6lCw^S+h{~wOt(55tF#rb2vr1yx225eNPqPV; zR1^omVnW{t&BOT7?@QZy&)mQ8z72UtjaSFsc)ZBD5q7IYSB5$!>QcKdIs!Z%W0SzW z@^4e?mK^>0{K57#vxyI3P{a6*@t@k=nv*MZXFv#hzCpU1_OmFv(i&k*o5NCjOp3@n{wy{HHxiQLPnj)D*Dz}RNbt&)i8dM z{9{Yn3dfVHw_*@@SFXtw%J@z43+>jO5BYiNUCl?^Y%~_5HY3}{kC#91H+O74=T&@s zKs#_>T3c-0taLSZz-2x(p0`LX=irWOe|Jhc5QFyCT!J-+8 zaO|7Jmzxy}u5!C@`@IFlx%o#X}d@1`u@cpA-3*S_P<8qw5E^`TY9DO z8ytI)e{C!LpU8ka);-j(!!Aj{U8xnGDrII^B& zNFQx76%P+8PBs2Id=R-m=hAnCMMrfNj54| zaHEFviZ$4y-ScMEPiX5xjmmg7zAC&pgj&_AB|@ld0VOaq>fC zy~V4;$Yml77};Ex5Z)zxW>Emrm5#C3T%O#=-AzfVf6b(Q6h*I5SL^HkE-#+}f z(7|G_E@)x=Dq|NmL7aiHM=A_*BGL!8gG;?e?|G;h_;PVtBFu;eU##&U*dr}u9xgsR ziJ;S4;P4Saw`?3%>elY(6A=_^DT6K(ixI|SHw;*+$Hf&$u3K;OT4hDK`_}0n; z#(hgzjw;n^q=#!)DU)43FIzOOR9l|&aAyhg_AT{=q5YZ%#~Y-b9x7A9tm7>HL{`Ma zqgeCWh%`jw`E4t`9$lV|b17IRzJ`{nHrG9FB_1Ee4#Lt}U&_{9YPas#)Gf?7^My*u z3m;eHxZQ?MPm9O4{6hMm4dO-ZW60_R(e@Zt-WdWL-vSo!-?SD7k73^IB<`G0@)gr! zSs$UVtfj+>FZ(p;W?n6pYAnbO(e(r#We-V%!k({^QrCTtJv1_)&7v|o(cw4>q+2Tw z+lZORkwm=s9f3XmDRw<8?)YMwE6kOz+lq=O@C^5!!NjZ+N+IQWTd@g&WRgaVdIs=i zto$d@Ty5IpN(t)flf)mwuQqzcvrHBOrF=&%dRI4T_2%v|0oBY?w4g3ehx}H5=>{1T z)u|g8J?z~bNN@KVn>*qV9TAQHbLW!Z-SX>ax>_FI+VQo1?JL4gDPxS|H+h~S58OT% z7cQWc^)oqndY9#BPCq{KxZfF9gpl&aut8HE*EoKQ>!8-P^>vVEcRzlHCGc;~m*hE~ zgNQ%`)#CY3EPwHLi8h_dR=Gto(J@_4YVF zF!K3C`uwpwb?xQSeHYq&tm4L*_sY@R&bX{%{n@{s-o1ArPl(Ou{&pG3CtjUH@kErEn{qBDB#;NZ=9uQ_d6U|(vH@e4v?XdLC`pO)sQBhfjN9Nx5KE40V^UU`+ zFMp%%vm`E?;BCj||H*;));#OS`5q;r^`&ZWrDt`07ay6-hc;~yH-_hX&kHh@|41Lr zQcokrA$}0?W{-WNzr9Cwy*qz;f0N@#e$4Ua_o?#D`_lMow80{;`WWrKbqKtrYWp-cx^8ZP4vUuMdA4JB`EAk1LqT(o6RlbZpm{ zUM21wqmOpRU<&UgmfTj{eEY(J4?^?e@1B1#A>s%RWps;4@F@bHtxuor?AoPql-*}# zFYypzJrBcz`^T!=5~f}F{+q3Ki!XbLVqYTm*RUuCi)M8y-6)pSe}>(n2rfp`XHL=T zeP8=^cC!1}dW(Jx8wm@hKfhL}M=g%T1Yx_u4Ho~R~1=f`}sJlF0#dGy_Ab)LsZ#0cA0id(+; z7=3FwDc8R7zaN-$TQNt+h(2E_#gw08#I&!Jo}TC9^)1Axgya6Nd%u3n)b#rQAztLV zgR*4nua_li%BX|&hqwIQZcz$8tiy&??+&fiIPG)0Pu>2a6T*6Sfd!XnpKBMp6`qqe z(QYxSznIC`7sG;e>5-$s%JM60x9t}D;DgiH84sOcVL&P7*hw@^G=;Q=F_fS#w8|Wp5 zieh=8R~ag1l5a6oj0W$UFjTLN4c^U?j$i$Lv|_GS+1HP~eJ*^;pd?QM7Mu^Xl>1!s z@#o>z-l1aI2M%tYH(=ofi|4({xeRYKs;oV%k~sHUBh`3=QmX9 z4b-dTjj^d-cUUe0&^A(ZA>VwYNW7=`C~HQF4b+i?77r93asB}w zLZqTh8zqA8!|3NxV&r|LsqZCJFdsZx_bBl6mx|TLBKhk2gU35cS_2nowTLr{0%W-3fqx)I5 z0qRVrNS^nyQ=uh{5*fM4n=F<*P?~0x^nGK(F7M(<7AI6W4?| zoA&7S&BoO$*FV!j`tab>%*!=O)+=tfLODU^OW=QL0vwuRvWQplrqXrElGwAtGg*V1VehUKWojbqS z_*f~d^q(M3KeqSajI?DQ{wD3Lxi?0ejGiO7!btt3rB5aI;Ir3*aOjH4((cp9UMh#X zo6(TWo}O|>j>WmB$fnT}hUef(qF6Qz$joKYN8f*;t=z=rXG*h-xMhKS-`y?$sqr)N z!*b|zM}BeTXT?u>v$BjP)<&x~7L6u1%4dvGssHn6_N+HmAHBcLDE&ipj5|MGlCw+a zsbb6XOie7OM{Nv>sxOr4Y;nmrVCF_K2rDma=LyV zl_&D>zE>~YZ`zk~Ss+E9u6#Tz&(Ne<24qQOM$;&8fwH z#f&3K&T^A~#ZbWF)@@4Gww zY@wfkH(#};c;t1T1NPyNrS>wKHB*V~nbW$I(Glbf@xBAdD1$w=%tiZ~R(p3&^eXmI z3eFd!3t}$zn=krdPWEj#UtdpO4bFLRN|)SD_6>0&d`e;QbUuH=*LpCzuDx$8nlJVu ztmj@>aJ#y;*`+_5*7>A`-9i@NU8^;Ag~mvFGFI5KBR@uSWvyew&hW=mL@quxyz42$ z2yZ-H$R`Bl&F27PYWbU8zR?En*xkR!u3qld6}cl z|G7sWqbV>-D!X8WzTblF`86KqxlewUaDVRR==-zn>-xlnV#|9)q^w*hUc-`$y_>5; zPvznwk?aax_5#nli}ay%)OL}Mz*;_vxF9z|3ia6#~;Fto@h)0|Imm=H_5s_dH&y7h2;3j$d!?MN4TtiBUa& znfR)lgPXj>COUaK6!KhwP`MDQYW%tRsRc)ApNZo#CXVU0EfX7Ht?UId^tolCa(Un_ z#Nvr`Tc(N1g`vl!iQvKxKFYu}F|x2jQ_pc}`sW-!gh!2NF|{m?1hv}=I3@^PE}9lW zh=}FlHA8k^F5HSZly_dgT>o5N_b>1K>qq)APTmzF8TP)OEA%=js(jrg{L!QSh!H1@ zyjoNmK0Lsm;L<5JcD#8JeDEPAy{fJdXA#zOvb1P1I(F~Pt=sv5GR~1vJd?UYC`FO) zLMw%DQIzM$m12BRhw?Tf_3f+lR3Cpjs{f)Zr*VMU1u^pgGiJ4Zz}^19Bg>-OKjPpL z*Y>ph>|ZS&7DcL|YxD!|CF^EAD3Jdm50Q8S3}+Z^*N9@pP^NES!P{i7V$ECnmuSEX zBCK{;WLv90uf6K7^5v zwco4}kD1p3uwc(BT;fj2gVqvzV1dueS%;6LPY3g&8;xEpWy|nl23iJrwG~I$LF@F` zANJ@FaVzj(11(2d*d+L{R2$;Gcf>x5S^^&)h9OpYpVRX#IrG)JrU^Mah}acbBc5-` z2XmH zwaMENc;*+IMV^xAiO;r(ZY5DVjI`m<_3>K35v9@YomS(JFHj1GPq&F4r7%fM+^#p} z$|=iAmT$J)hA3qdF4!)%m2yb-EV5H}jBXGA{B36Bb6EK3_gm-e6wOMbf^NWqoyxJD zPwO+;)*Xd~Y|2Wz^swct1po1}W4@>GkweHcT`Ve%A#^=moJSmU&*=nA&6Z*1gwHe#s9-^~;p8d-MS^DSNpk zVIOTGuJY+`BT()a{3)wAd9uUc}@C}m|osLc__w9pIzt}CJ;KSIR>gA0Y zx+=1w-Dl_lah9=9mloo|#hxP_HsHd!k+Ymfgi_JL$5_ppRdlHCdE$s(Eir{deYaoz zy&kGXe)s6=5wWBq^70TCoON=oKD{zr-0H4%0qqWK(@%AuX8X4V4M}=kP7BK~`{G~_ubd+)T8<_M9iMRHPy4leaGAHyoOlSwDPFvR|)B-ON&EyQ`h7z z*!YFr;-vKPZF+iF+N>SJ?LIe-ioFQy`3M$yk@w|mNB4Q;RQjUb0vG%rGxmJPbPFd} zTZP@`*{AImUhrW}pUB=qG`bge*zOZ>OxP+TY;$RG*z~Aznfpo|yG4}rxiPM1Bk#G_ z57~W&9uw0L)^n<~=w9FtYwn`?p4%;!9uo%{`*tJ!=gCD!u82_W7RTYkwQGq-)sH{F zAJxI`bMu(URRv+6!lD2yau#fGf61mH$L$tQ$913ejbb_v4D!N9twwzpIW9UOtWy2B z7*fTdK`GC(`XtsFm=tir zJ1y+VqoQI})WZ$gLRIT6=$f7_w_*?b=&0xh3tuzJ!Zw`jxS@Af*IJ2oi?_C&7Q5Fm zv58>=WF5Nw_M3OhQ@+vmuy1WUEo{^=@dtA@RE8~|r))Lz)Nj)4VW%DwRlO1WQfZ+E zJS^E~`hv-Ji?_C&7W?sIVl>0vL|C>^OWXMho`qdk+rz%K?X<8?$Hi%c^(-RWFt*$B zKQE{5!3XMSL(QW4@wq-&-QKZsvS{Xmn2(P24WdGrf1m=6*#KsfH|9x}wLP?soL%`SY*8)g}@4JX5VJ zqGdH>QYuhT%cH)I#m_sJd-UX{!o|Geir7}op%%Wi`(rf+ci&Uj^q%?AXY+Ihr+rr6K0ZDGN=GWRnvx2WyhGP^}T__)C* zsM1=eKG%!k?cUbJ(5FF&$)*s&kGGb4^DeAzY)J0hEnnuKjb;fQA z{hD9QlIfTAcF@Yu+eeJ7;m|! zVXK#UnZB(OudCII-O?maQ?p)qp)XIyT7LSdbyoj`!BGPv6QWJKj`q*7Ff9MTpAnvw zeIsJTini;Ex&H~B8}u;+BXa$juX@t$9C)tbl6$mf*H%G8YF+vNnt`0ipcU~Qcj&RM z=-#%T?Jnsvv{GS0*SV`dco++v*JyHIn>XqAxt^cx8v%l!Zkrx{are}@+EwTI=+%@x zPr9?8!!>idxu!!}kMs>o94ZWR4$kAK)nU6l;!Z(FzZ{dLzBiAfI5%A>ErQ(~Km9*l CW+qPn diff --git a/package.json b/package.json index e086ed1a..67dc22f2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "dependencies": { "@aws-sdk/client-s3": "^3.679.0", "@lucia-auth/adapter-drizzle": "^1.1.0", + "@oslojs/crypto": "^1.0.1", + "@oslojs/encoding": "^1.1.0", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", diff --git a/src/server/auth/password.ts b/src/server/auth/password.ts new file mode 100644 index 00000000..82768624 --- /dev/null +++ b/src/server/auth/password.ts @@ -0,0 +1,53 @@ +import { hash, verify } from '@node-rs/argon2'; +import { sha1 } from '@oslojs/crypto/sha1'; +import { encodeHexLowerCase } from '@oslojs/encoding'; + +async function hashPassword(password: string) { + if (typeof Bun !== 'undefined' && Bun.password) { + return await Bun.password.hash(password, { + algorithm: 'argon2id', + memoryCost: 19456, + timeCost: 2, + }); + } + return await hash(password, { + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); +} + +async function verifyPasswordHash( + hash: string, + password: string, +): Promise { + if (typeof Bun !== 'undefined' && Bun.password) { + return await Bun.password.verify(password, hash); + } + return await verify(hash, password); +} + +async function verifyPasswordStrength(password: string) { + const hash = encodeHexLowerCase(sha1(new TextEncoder().encode(password))); + const hashPrefix = hash.slice(0, 5); + try { + const response = await fetch( + `https://api.pwnedpasswords.com/range/${hashPrefix}`, + ); + const data = await response.text(); + const items = data.split('\n'); + for (const item of items) { + const hashSuffix = item.slice(0, 35).toLowerCase(); + if (hash === hashPrefix + hashSuffix) { + return false; + } + } + } catch (error) { + console.error('Error fetching pwned passwords:', error); + return false; + } + return true; +} + +export { hashPassword, verifyPasswordHash, verifyPasswordStrength }; diff --git a/src/server/auth/session.ts b/src/server/auth/session.ts new file mode 100644 index 00000000..0293f26a --- /dev/null +++ b/src/server/auth/session.ts @@ -0,0 +1,67 @@ +import { env } from '@/env'; +import type { TRPCContext } from '@/server/api/context'; +import { sha256 } from '@oslojs/crypto/sha2'; +import { + encodeBase32LowerCaseNoPadding, + encodeHexLowerCase, +} from '@oslojs/encoding'; +import { eq } from 'drizzle-orm'; +import { cookies } from 'next/headers'; + +import { type InsertSession, sessions } from '@/server/db/tables'; + +function generateSessionToken(): string { + const bytes = new Uint8Array(20); + crypto.getRandomValues(bytes); + const token = encodeBase32LowerCaseNoPadding(bytes); + return token; +} + +async function createSession( + token: string, + userId: number, + context: TRPCContext, +) { + const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); + const session: InsertSession = { + id: sessionId, + userId, + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), + }; + await context.db.insert(sessions).values(session); + return session; +} + +async function invalidateSession(sessionId: string, context: TRPCContext) { + await context.db.delete(sessions).where(eq(sessions.id, sessionId)); +} + +async function setSessionTokenCookie(token: string, expiresAt: Date) { + const cookieStore = await cookies(); + cookieStore.set('session', token, { + httpOnly: true, + sameSite: 'lax', + secure: env.NODE_ENV === 'production', + expires: expiresAt, + path: '/', + }); +} + +async function deleteSessionTokenCookie() { + const cookieStore = await cookies(); + cookieStore.set('session', '', { + httpOnly: true, + sameSite: 'lax', + secure: env.NODE_ENV === 'production', + maxAge: 0, + path: '/', + }); +} + +export { + generateSessionToken, + createSession, + invalidateSession, + setSessionTokenCookie, + deleteSessionTokenCookie, +}; diff --git a/src/server/auth/user.ts b/src/server/auth/user.ts deleted file mode 100644 index b7947e34..00000000 --- a/src/server/auth/user.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { auth } from '@/server/auth'; -import { cookies } from 'next/headers'; -import { cache } from 'react'; - -const getUser = cache(async () => { - const sessionId = - (await cookies()).get(auth.sessionCookieName)?.value ?? null; - if (!sessionId) return null; - const { user, session } = await auth.validateSession(sessionId); - try { - if (session?.fresh) { - const sessionCookie = auth.createSessionCookie(session.id); - (await cookies()).set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - if (!session) { - const sessionCookie = auth.createBlankSessionCookie(); - (await cookies()).set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - } catch { - // Next.js throws error when attempting to set cookies when rendering page - } - return user; -}); - -export { getUser }; diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index 8ce77282..b7ad2d67 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -1,4 +1,4 @@ -import { locales, users } from '@/server/db/tables'; +import { type InsertUser, locales, users } from '@/server/db/tables'; import { fakerEN, fakerNB_NO, fakerSV } from '@faker-js/faker'; import { routing } from '@/lib/locale'; @@ -23,15 +23,11 @@ const insertedLocales = await db .insert(locales) .values(routing.locales.map((locale) => ({ locale }))) .returning(); -console.log('Locales inserted'); - -const user = { - firstName: 'Frank', - lastName: 'Sinatra', - birthDate: new Date('1915-12-12'), - phone: '+1234567890', - email: 'm@example.com', - emailVerifiedAt: new Date(), +console.log('Locales inserted:', insertedLocales); + +const user: InsertUser = { + name: 'Frank Sinatra', + username: 'fransin', passwordHash: await hashPassword('Password1!'), }; diff --git a/src/server/db/tables/users.ts b/src/server/db/tables/users.ts index e5a0854c..714df7c1 100644 --- a/src/server/db/tables/users.ts +++ b/src/server/db/tables/users.ts @@ -34,5 +34,15 @@ const sessions = pgTable('session', { type SelectUser = InferSelectModel; type InsertUser = InferInsertModel; +type SelectSession = InferSelectModel; +type InsertSession = InferInsertModel; -export { users, usersRelations, sessions, type SelectUser, type InsertUser }; +export { + users, + usersRelations, + sessions, + type SelectUser, + type InsertUser, + type SelectSession, + type InsertSession, +}; From b2d052594e84629e6eb400dff42b08d8cd13c172 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:24:54 +0100 Subject: [PATCH 26/93] chore: remove eslint comment not applicable in biome --- src/server/db/seed.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index b7ad2d67..854a0639 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -14,7 +14,6 @@ const fakerMap = { console.log('Deleting existing data...'); await db.delete(users); -// eslint-disable-next-line drizzle/enforce-delete-with-where await db.delete(locales); console.log('Existing data deleted.'); From 15df84369873df864888d027d36d2cc9a6729f26 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:27:33 +0100 Subject: [PATCH 27/93] feat: add auth check to auth index --- src/server/auth/index.ts | 96 ++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/server/auth/index.ts b/src/server/auth/index.ts index 48702a2b..4b455b4d 100644 --- a/src/server/auth/index.ts +++ b/src/server/auth/index.ts @@ -1,45 +1,67 @@ -import { env } from '@/env'; +import { sha256 } from '@oslojs/crypto/sha2'; +import { encodeHexLowerCase } from '@oslojs/encoding'; +import { eq } from 'drizzle-orm'; +import { cookies } from 'next/headers'; +import { cache } from 'react'; + import { db } from '@/server/db'; -import { sessions, users } from '@/server/db/tables'; -import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; -import { Lucia } from 'lucia'; +import { + type SelectSession, + type SelectUser, + sessions, + users, +} from '@/server/db/tables'; -const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); +async function validateSessionToken(token: string) { + const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); + const result = await db + .select({ user: users, session: sessions }) + .from(sessions) + .innerJoin(users, eq(sessions.userId, users.id)) + .where(eq(sessions.id, sessionId)); + if (result.length < 1) { + return { session: null, user: null }; + } + const firstResult = result[0]; + if (!firstResult) { + return { session: null, user: null }; + } + const { user, session } = firstResult; + if (Date.now() >= session.expiresAt.getTime()) { + await db.delete(sessions).where(eq(sessions.id, session.id)); + return { session: null, user: null }; + } + if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) { + session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); + await db + .update(sessions) + .set({ + expiresAt: session.expiresAt, + }) + .where(eq(sessions.id, session.id)); + } + return { session, user }; +} -const auth = new Lucia(adapter, { - sessionCookie: { - // this sets cookies with super long expiration - // since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages - expires: false, - attributes: { - // set to `true` when using HTTPS - secure: env.NODE_ENV === 'production', - }, - }, - getUserAttributes: (attributes) => { - return { - // attributes has the type of DatabaseUserAttributes - username: attributes.username, - name: attributes.name, - }; - }, +const auth = cache(async () => { + const cookieStore = await cookies(); + const token = cookieStore.get('session')?.value ?? null; + if (token === null) { + return { session: null, user: null }; + } + const result = await validateSessionToken(token); + return result; }); -export * from './user'; +function sanitizeAuth(auth: { + user: SelectUser | null; + session: SelectSession | null; +}) { + const { user, session } = auth; -// IMPORTANT! -declare module 'lucia' { - // Has to be an interface - interface Register { - Lucia: typeof auth; - UserId: number; - DatabaseUserAttributes: DatabaseUserAttributes; - } -} + const sanitizedUser = user ? { ...user, passwordHash: undefined } : null; -type DatabaseUserAttributes = { - username: string; - name: string; -}; + return { user: sanitizedUser, session }; +} -export { auth }; +export { auth, validateSessionToken, sanitizeAuth }; From 9c572abe38b0e228b66245ba9582aa750eadf5f3 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:37:32 +0100 Subject: [PATCH 28/93] feat: add initial schema --- .../db/migrations/0000_mute_wrecker.sql | 55 +++++ .../db/migrations/meta/0000_snapshot.json | 229 ++++++++++++++++++ src/server/db/migrations/meta/_journal.json | 13 + 3 files changed, 297 insertions(+) create mode 100644 src/server/db/migrations/0000_mute_wrecker.sql create mode 100644 src/server/db/migrations/meta/0000_snapshot.json create mode 100644 src/server/db/migrations/meta/_journal.json diff --git a/src/server/db/migrations/0000_mute_wrecker.sql b/src/server/db/migrations/0000_mute_wrecker.sql new file mode 100644 index 00000000..7c770db4 --- /dev/null +++ b/src/server/db/migrations/0000_mute_wrecker.sql @@ -0,0 +1,55 @@ +CREATE TABLE IF NOT EXISTS "coffee" ( + "id" serial PRIMARY KEY NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" integer NOT NULL, + "expires_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "users" ( + "id" serial PRIMARY KEY NOT NULL, + "username" varchar(8) NOT NULL, + "password_hash" text NOT NULL, + "name" varchar(256) NOT NULL, + CONSTRAINT "users_username_unique" UNIQUE("username") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "locales" ( + "id" serial PRIMARY KEY NOT NULL, + "locale" varchar(2) NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "skills" ( + "id" serial PRIMARY KEY NOT NULL, + "identifier" varchar(256) NOT NULL, + CONSTRAINT "skills_identifier_unique" UNIQUE("identifier") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "users_skills" ( + "user_id" integer NOT NULL, + "skill_id" integer NOT NULL, + CONSTRAINT "users_skills_user_id_skill_id_pk" PRIMARY KEY("user_id","skill_id") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "users_skills" ADD CONSTRAINT "users_skills_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "users_skills" ADD CONSTRAINT "users_skills_skill_id_skills_id_fk" FOREIGN KEY ("skill_id") REFERENCES "public"."skills"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "locale_idx" ON "locales" USING btree ("locale"); \ No newline at end of file diff --git a/src/server/db/migrations/meta/0000_snapshot.json b/src/server/db/migrations/meta/0000_snapshot.json new file mode 100644 index 00000000..c13352e1 --- /dev/null +++ b/src/server/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,229 @@ +{ + "id": "d7625206-2961-408d-9420-6a6f22af1030", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.coffee": { + "name": "coffee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_users_id_fk": { + "name": "session_user_id_users_id_fk", + "tableFrom": "session", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + } + }, + "public.locales": { + "name": "locales", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "locale": { + "name": "locale", + "type": "varchar(2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "locale_idx": { + "name": "locale_idx", + "columns": [ + { + "expression": "locale", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_identifier_unique": { + "name": "skills_identifier_unique", + "nullsNotDistinct": false, + "columns": ["identifier"] + } + } + }, + "public.users_skills": { + "name": "users_skills", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "users_skills_user_id_users_id_fk": { + "name": "users_skills_user_id_users_id_fk", + "tableFrom": "users_skills", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_skills_skill_id_skills_id_fk": { + "name": "users_skills_skill_id_skills_id_fk", + "tableFrom": "users_skills", + "tableTo": "skills", + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "users_skills_user_id_skill_id_pk": { + "name": "users_skills_user_id_skill_id_pk", + "columns": ["user_id", "skill_id"] + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json new file mode 100644 index 00000000..96eaefbd --- /dev/null +++ b/src/server/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1731004618013, + "tag": "0000_mute_wrecker", + "breakpoints": true + } + ] +} From 4ac843c4c5f7a87b3d315bf841ee97178ab2c880 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:38:06 +0100 Subject: [PATCH 29/93] chore: remvoe lucia dependency --- bun.lockb | Bin 268556 -> 252900 bytes package.json | 1 - 2 files changed, 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index cdd4f75c66a942f01518667e18ee798c7c6febad..74488d4e523a53abb05642fbaba2fcb4f52ca91f 100755 GIT binary patch delta 43992 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%0ArCd8{ZELlr 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 delta 53969 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 y9N(gu89$qbe`a$NDycC&gf|^;&#rA|`3 Date: Thu, 7 Nov 2024 20:08:13 +0100 Subject: [PATCH 30/93] chore: update lockfile --- bun.lockb | Bin 252900 -> 254412 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 74488d4e523a53abb05642fbaba2fcb4f52ca91f..1bd2d7f17f9358c76086dc4b23e52abd444146ea 100755 GIT binary patch delta 44676 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 @@ -202,8 +204,10 @@ function Footer() { prefetch={false} target='_blank' rel='noopener noreferrer' + aria-label={t('NTNUNexus')} + title={t('NTNUNexus')} > - +
  • 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 z
    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 Date: Thu, 7 Nov 2024 20:17:18 +0100 Subject: [PATCH 31/93] feat: add missing oauth dependency --- bun.lockb | Bin 254412 -> 254780 bytes package.json | 1 + 2 files changed, 1 insertion(+) diff --git a/bun.lockb b/bun.lockb index 1bd2d7f17f9358c76086dc4b23e52abd444146ea..b5b57a454f0d826e607670dbcd9a135b57f0e8ab 100755 GIT binary patch delta 42827 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 Lv2gj$>BIjAwE{K< delta 42651 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 kx=ktH>El@kHlBAMcdnWX|11?gK{t$Fc@^ Date: Thu, 7 Nov 2024 20:22:00 +0100 Subject: [PATCH 32/93] fix: oslo dependency --- bun.lockb | Bin 254780 -> 269652 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index b5b57a454f0d826e607670dbcd9a135b57f0e8ab..da80c8e61a85bce135166705803789ac838deec7 100755 GIT binary patch delta 51439 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 YGydFn2`4$Kb0Py`H9bk%ciN+W2mh)JMgRZ+ delta 41719 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 diff --git a/package.json b/package.json index 797afb09..f04b1ee5 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@aws-sdk/client-s3": "^3.679.0", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", - "@oslojs/oauth2": "^0.5.0", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.1", @@ -57,6 +56,7 @@ "next-intl": "^3.23.5", "next-themes": "1.0.0-beta.0", "nuqs": "^2.0.4", + "oslo": "^1.2.1", "postgres": "^3.4.5", "react": "^19.0.0-rc-fb9a90fa48-20240614", "react-day-picker": "^9.2.1", From 0999ed17515bf8424502cb8fc430909c7cdee3e0 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:35:31 +0100 Subject: [PATCH 33/93] chore: update lockfile --- bun.lockb | Bin 269652 -> 269284 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index da80c8e61a85bce135166705803789ac838deec7..3232640ee72dbdecfa40277eebbe2fdf4edcc6d3 100755 GIT binary patch delta 44335 zcmeIb2Ur!?+68=O;3!8WV)uANMWccp0Rbt;USq+Iii(PYf(?5Ed#|Hzv15t7#ey}~ zSfa+5jtNndc%w-)iRR{7qW){m?BSr%8}Iks@A?1#d2;kUYrkv1ySJG=`!Jsz^7<~r zYkoDqZ<-$*T=u%VZ-?3U>RsGEnwK)gHHYeup)2=&>Of=+6|ZJ1Tx_YKxWia;_rxu>3jq%kOIe& z5*ABUU?Q**us)Cpl#uusm=DFmLK@sXR+d3whVjjP&cK=O4I zll-88$wT`NiAx@soH!7D&2k0RlOOy_R3p+z9S3CJiIEr~u@sOs@XQbOPsa_^FLQoM z;@d#B=?sZUK$fTtkTq7Xx{;v(AQP+rWX6-?lKLbgBh6|U1@Z#Vj0Vea-ayvKNtw~g zDFH@K^8%UEA8Q(TLE<(bGdM@$Kp-<3B{2}lj24sl*x$(Lbs#hPp2V#{COlx+VCK(~ zlrWT|-I8(zRm6>O94+yy{Oc8 z0crmz!m!T*vS!`|mIiJHG9wFt%y@EK((t$;DHh8#5Uhb-0}}@hLC!BWGTN+cV`JtT z3Z#E=U>@Kx__M7wl$dQhTH*kSy@9k-6(d|1=r-u=ti6(xEk&Cd;qw9WApO(%Gs z^tM=f4omJia1?Ua+=%!HnuY;~CC61w7%(((%3H=T8wg}gJOi?(F10X9dJ{Tpa23L{ z#ts3QkzI%k3jZq_$+Zr>W zxhA|gRX;z)x@K5&3O1)?Id!LG&)wz}Y|i24qGHbF-DF1F09m5CKz6DisXxFP!8ZOW z+UN_O;8`OZIvW{&0_1T0POc?;x=_a&unGDta zi9l9q4}6ggA>O4&m4-lZGTEBtbvW!>H*^K(2-hW91s4fnWv#dl~`8 zKO}s@v+GRM+~r7=1F}8 zkbP>Z#6AOuR87IsAAh!w5kU_Gr$N`gMwMLwG9%4^>?22kEKxJqu@m|y7*%{78RAra z0my(lpp%)VQ z2yMmy1(FQ^!_Ya7PebSU-T`EWzC&r5k&8fPe6K{aZ>@&T49)=3e%&C9K_+M!Y$WjW zAfsyMA_2DfNFWXS4>5*S8zA`*AUl^Ykl|xs=Kwa68S@7+p~66K;FF<7&2=1R#PcoY z2ijjqfrB~S2V_DUfQ(Q8-vCC8Fwh^!8kqvVAaLC{qe|zGHQM?Q=!|C}kbUF&ctdZ3 z01Iet>vU11YI1K``ReQ;mcaX|Qj!QR0)(%fNro z6a!a*XTs;F8woyxe_7}~2aZUN8)C711Kk_?Z6v^ih7IF5u~@c4_kq3|$eNu3EWz=g zBn`U)S#|Y+%=zxwM#M{i4(Mi0|(6EK@*B6v3GOPdDG zk@^h+_yJDYpKy>XQ7vazFy)a zAPeBN+$c~l=hxd$Z-!L?$tC22I<-D26l&X`?>#>Wjv=rhW)O6r?|o`~fP z!m;7|CJyXL`^g)O2Kye!S?Mc@BjOW!#lt@#Ic{VeaKPI}z1Q8S$CHveD0XOk)&6m# zM#%uVHyQJ3Qo?|QL9xlngTQlsJqn%^cfrj@Pw6Z9txd9 z&^Z}=44oa}9564t@?&@~$A1-Y$95xOBcLnXQPc!8_%@IcSKDcH{USg+^dEraEkHIT zFTWFq$60>dh4y1tz5xew`Z17Wz+Xnx_#H#XHBek~FZ3Gl^f#}IyrAn1oo-}cRBTcr zn<5u@#@A@CQ6mrF&v194lOM$P#OEsX6!!T$FklT_lZKxHX*guR5pmBUvAxhWEVrSP zubyEN{J8lAWPAy45 zmj6CXSNzh5B@xJ2N&z`4UgSTp7%P&mwg90kd~K{(d4UdC-^(&) zjjO=o&`(O-3M>YFF_4`lWzUU58*O{$dwb+j>S}2foys6B6W_-*yRXygWwlrWG;iN9 zrM{Nt>$I(aUK^p@we;b^%4eG0s9qWzrqtGO+fHt<2eclL`V>8|Q%iAK6>7+f8+tijY=oe5VDnyNp%p>_6)vg+zV(W zDo0wIV5NJDHp(I`t%lRu0JW8*t*jAd-3?b;-Q|v+QCD|0gv;=K2d)GyCN$g~zo zM)z9`SG4ZB3s;oxa{5>-t#sGiY*!XsM!71LHC-d%YOZ+)guCkwX4A8rS=0{MuGMfk z_2@?9Vy2^pj3{qv%W68^%bV#W!-d%W!%6AM*gPv(ERE44+_dzSAzEgb)4CK)ZEa=c zFy)#S9qv?ow6t)ity4vdrH~cSGV%S9X0PW|ifhsMZmXr$b6V#h zr=eO_-7xDJxbzTKhd(Zsc_4c5m4d{^nN7-Rqr42=3=wnnIRRtF7)T8g9B0;EK|@ zLvXdyUA73l8#W13+G)|voYp~34)vI3Vb&vXwbWgC&_x>2Wor$WF+KSl4{?Xmj7)l(30Ge&t9h8!y`C9eYq+}T+*Y_+>voUf>aKI0>zmvbxD3wLK!&xo zhpUU;;bOyst-GMb=%G9tS}d`;YXDqEW{<$tM$fFHk;Q`6O3kg^s1%IZH?x&fnWNcT zJ5h_#_^zR)wRYN4Fiuz`ix!J+epItZIc*QYA!}#>pUoj!T9i|nsAb~&UCrLcsXlC? zb#4=B?b#Fo#e|=g0PRCR59=DJL99~cQ_bGiX)A|ygK;QY`ic;DDB+rSl`v(emI=>q zp)+fW7F#LUTCO=pA?I7AmzLJfX|44ZyX?w#VT#b~?VUEC7RD-s`8^_7>93`=cPc+< zneeIC(pZ7?ncubuS_i!-H^PE#KR`n>GXq%jDrlJK(*!l~!4tn_-Y0=S6F_Y+u$C6% zv_66UmbNk`Ozjb^EsKd%)@sq+oYp*@F%4=l-8i#kLO%e#p_bJx%$5(GkX35YZgdTC zhtim|a#zdj?oQ0uLO)yG}6Wj!KoyJE6QEYP{Sv9GV}5vDBHqGO%P zO)V|fX)E8|V!@bYKdcmN>j4eJi%Yt1u(C#r?&-9B1&*CYw@|#a%$`o``>4G@7Tfj| zF7z8r`IslH&0<*w6xFs4F7&oktIpqn)>!XqO|$WxEI>vI zk-^GbEjrGrKI^3|i;J|4?46^%ZRepG^T3T(!PeYy=qGw68o*_AdR)HPHb7&BF^teh zGU=zq_=VXVeT=Iy^s-jLN_#D>uhTXU9A-LJ?S>G|9`8f}qvM^nfql`|;Pv{n-G+un zGkTq^ZoE-sy@y-pKx?geM}=9l;KIZdA7*QwfCmdkXpDi4n!TSBy&}4w)9UQUF^5YU zTMArE8Pjd^U|R+>%s{M0l;Dv|tBQ%Bzf)PPW%hSkuk=Ux^|ItoG}cTbe;uK*+0aCO z!59UZiB9FBW>0cjeUf;2j@Vd3L~dYu=QJLUA2`%!`%n4+pdfVv)+TNHrvQn zWuRI38w*0*sgSmwgA08fuEK-Z%rxx^7iKM%qGB*ECNl>*Z3PFTzw5d2i4RtywCF)j z>s)ZCC4~4Iu4Z}&J33rDB0~gdjxbz9p`cJs@ ziPP46M2_jqHVqmZO>cVj*a&Ueut;_PIPL1NNL%6YM)z{lM^Z~@oVK8~3078VX~Uh? z2jn<9tld+XILFC$xHzHVk_i_z`BGVQT6BY}5nal9%|24UZ5Zjaj-OzLy9gJn5tm8p zLp&y;RJy_|@qW-6Xj!Ae-QkGWVn&8pYfsV(J2K2R9WHhQ434N^^^-|j=h2bYUX$4y zVn&Cl$0lo6M@Ql&#XiPqn?J=!!%e%<^q+3MH;3bjwm@v+ZQ`uQ;JLaMrTt`_%?v6 z4LRG#aA9WQ;6rS|bBxj%H*IsY=m}2S4RDxDjTCILb8($vYStUjjCdN%Go~l2zF;oY z(k41>m%-tJ2_aF3HrsroH|Z9(hS2K453_CGVB0iktqmlE4kDRF$ON&HKFPs!Nr+NuMlhPH2wAmq0+Nm9?Q_UdU-;ZTP(?X z?AzdKtaJC_!adxKaE}$nI5KK>CN$Qi(GrEV%vnxb|CQ)RCTD#Q8cK<>MXl1Km>%v9 zhY{;-xDqt)nc?oMO{+a{AsfhA(KWJuY$M>}STfSQ3@u#OaNTFko6cD!COk~(u4T@3 zDg!n9Jg4o)wMYv#7R{$$uq}L@#Zn6z>b+jDZ3wg~&@h$O5AlE!2}QqBv*lfH7@~vv z23zYwi_}*3#To(^I}jom7~%n?nI7Z@t|%Q^^a7`C(FUVsF;daP?m=Uu`mL3%#@mL5 z>r`A=31~Ipr!She^Vu3N&mKc-r29crTW-{r4Ue?V-)KyQ=-3s5)pH=VNM0RiPFwR$ zhF5Ma_DF~a6b=9zuf}W_p&3zesFm4_sRMq-)m(3AwY3;rR2+tj<-&S4KG^nzJTwok z=e84D3@taWFKxND8kL?)AG&Wz&A8~84h_?J>oDsNaCP7mVym;wD71$jdc4$7f2f`# z&}2GTPkx8S=0qVPLOizTOlANyqtkM!IRcG^K${K-@z`O+k9*nH!L~@L<k2Is7I}7CEPV_P z^Zg)bnDFAmlx375TUdtp+qqR=8)+v)8B)SfU-kbX^fYK03el50|uCNyM1=AL| zFFOv@cW-DMq^SLfVC!CJ=+#*I?!nbS&tt9qW-77q!L~8bm>+ae-(cH5Xo1ks|ImH! zOG|X-iov#s45Q5G2QeWYP#DbUFsG%4NKlkv97=qZjBs)z$^tTSmlKR5n7BcdcHV^C=-(R{JpPKacCHGF%N$ z=$B=1rNM=j8m`;fT*OI}n*$dv-{5-#F6<4$RrQqVI}R>vL4vyg7uMwVVK(p6X7^_| z=?|?|cKOnwg&SJsU^VNsc6D2%(owT-cdDn}(>iaDv=usI%%4X0?EI} zhts8j7_p;qicaTl>z)dcg zt_9hytJ$t{7wH?bGu&NwFtvSf;ob_aKeAo*FPYrTY}XaI8tQgMKQLWgvR!MkT{q#X zuirBGUN%<`^f2rfNAGo7Pl0Qt#q15U7W|NM{Uf~`2%MPh+L!Hmn(b=*G3{3F3wPHYOf3_x#(KJCJ~3T=vt2u~UH^oup>9|E zQ`41_?K++9vVA6fZ7t!diT1?=jY%yPS`}!<4aIF}9RB)hiM<#5VW(}==f=p@Z@X-F zp>Yy$*KV{8R;p;3hn?2-U$DROwnxb{#~G#wB~(j0;*Qp--QoH(Yq(@VYf_V7_3&5P)nk#iXCT?#u{L4GZ=gjVcUtRzt#5H253{a-3)_@% z{R$VZso<)2O>b<`ik83bX(gCYD78E_bc{BQ^} zI10i9$4Gs=)Kh_sX9^@2WEO;WbEK{T8GZ?b8Cz*-YZ$DCVuNgg&|oWsA0i{z4)KIw z{@3**KxPPYt^Og>?ifVTvQ9Z|CE&WDYjZw7W@BOuplEkaS5KjmiyAPuL=P(XSbRdK;8R0*V%q3DoYqS4=O&n=MB99PgmP5`Iafljp=3!E&GjxF5)X2k7|%((VX&4!x5=>Sut=$ax_CTi)lF#6rL< z=(M|`phwa0I}mw*ccsTYApTqK^<^Fm z*g^)(iHhd=e#K~dw2{6EnD%$jPgV`^^`sMWnL0j1+FOBK-QSQr5vG;`5(@)afTB{j1NrL_i?{B;Q9x->4#)`K z1Tq7DKt@zWVs#+?TLPrcU;8lP5Fo>aN#fZV#|CNm zGNjiQe6fwUNjoAV+6iPtyU1&IE*n=&yP?r~57`%x)_Y~BebS#u`wXcQS-hh_rg9v} za3`cck^E_?=Rjk9e@_~m12W)wX-H(ii&Fo0NW06@j>rsNk$O&~-6xX&#A?jvI>CfL zl>tAO204)tW`bt~Ujfl+EZ3!7PNdy8k|)ycA3(0y-nOEqS7%ZNE~i z0IhzKtd82zE5)?8ZhGiT;(h5uWJ13I8PD%h|CjV9l7A@qN0R?{$R4kWF1y0`QvvL?$&ok%+$sS`PbDog#z9TBHS7Idq;$$;y>IO`Fls?Pdg$FS&jee z9g#iJxBo~G+uJJ5l_2r3tI!=&5XC!z7}p6zeQ}<|ITBT)K{OPj zqCt#|29ZUgvGD5*!nZSsIh{c?6`3TyAQ908L~}8t3yA4mK-?kGLWFh&5z-aJ%B~<< ziQ6P@byeOFEn}2sN|Z>80kJd&7EfYe(N;9=2BJwf5Zk+fXfGa;ct9eiJBW^Ab9a1g z>JGxQ2Z(49-2+6Y9w3gA=pt;fAUtA0B*lV=5eG>eAmQB;M0b(U6GVJZ5SK{A3VSaQ z#e0Dm*9$~1ah}9E5>7s@!Y>YlZybm@aUc>zCW$XdMDzjCU(DzO zVtOACcSt0O(7qr-`hr;57sNnuo5U>=E#g587HRPymd1m4LLyl-O#sm(0mSwM5W~bn z5)Vkk^aC+MZ0-kQQ$G-%{XvWp(fvVm>JQ>5i7~>K2*M)~L{cJ%apE9}10=kYK%|I- zBoOgQATE)ZAnXG`6dwR$+yD@h#Ca0uNK_pNVu~0w5X8uVAhJkI6Mln0_znUwXAp=P zB9p`yBq9cbm?dTm1~Gjwh&v?ah|nP*LWY1?IRwN!aht>~5-pNJED&kQAeJVBctS!G zO^1SLG8Dx2p&%BEha?`5h#3Y#h|R-5Y#Ii_b2x}J5j`A4r{N%ul2|TmBS3hJ0Fg8T z#7c3H!~qiCBSEYd2_r$oj|6dvM7pq#0#SSvh;gGptP|%+oFh?nG>8pi)MyYRM}x>B zu~GPq0pU9a#GEl8Hj7LWUyz6x3u3F7F&4!1u^{e{*e*iHfe0A~V&ymxJH>4hw@9=Y z4`R1S8xLaXco0uW>=8{9*A-CKzu9ClQ>7B>Uhr}Hbs(}d6K&;e2{48#hxJ9DHA`ridv_&A6E&}m{#C_3pF^DFML2O?P z;-BIni3cQNmVo$|*t`V9rX?Ue1&D_tS|}ZrM`AbOv9K+Lx5rX=CoP5d6LFBl0TSM6 zN^|v@BKoB%9YlN@m`h+3Mc9|Yr1&xr{WkYC&;c!|2J0R==Fp`iGcP)IaggG8FF zL744pkVp~nki-KLG3g+RiOuOCHl>5`TnoZ3qSu1xv=+os5)NTo2f||=h@^EOyv0Ef zN-0rbJ)pEmAe0d&2tLBT0Z>*9CX^HB32zFYw*lqFC_)8sg-}uWZ3Os=2?RfpNvI_J zHvuY(8H6h02BE45-3+KE77(h7+k_gT?iPT*NCSwaTadXYTadW`(R3^LK#@)e5)T35 z!B%+3Y=d{O*t`vVh)}izLPa#8w%APw6Sf_Ia1l$05C;j7qQFjoQzQ`Th!cdm!oCYo zPYfp17v~8LgwJk3LotfbNL(Q_7JlylnurO6rXrKjO!)5sG#4`fV)`Cr?#>=$u7wEQ z3nFAMh?RRmv=X;T+#=CpABZTCwhzS8eITBYXe*lT2hn6di0%79v=0OBZ#F2Z&YgvUV;Ne4m1h=U{!knlbPqPs{q1S0+rh)X15 zh5ayy;)g+uI}D0LIA901ySNI(T#ES`p1d&PTC;Z<9^cOSURWcO! zjJn5^Yc|C@!{@xxQMJB-Ctsc!S1u~`75sqR-Ddt($@Gj%7nNR~I%obJw22u}SC#6j zdQ;2z;G!~B6*aCY?jqkc<$*Qt1{~HBh{nkfp5xv&{ut0Spci8osveX1SDzM$C*LR| ztZDDSjG@}nH{-)^l_rYyX$B6UFwD2glJAwt)|r!vO=tjr#s zCpC`Gxh$6X38H= zxoXmmkNn=itsp)q_tnaQM$ZFJu`HjwOFf+7*F2RdOwO zn2-tc3_%%?e4yDpvFaOj(G=a-y$K(stKK5u8!@USSoD?oTEgH6*N9BZ^9 zga_K#H0Ci-U$|>aZn$}Z(hnq0ys>S}ryv{+FpVE-r5D$nA zk_VDc#3ZT?rLY*4sFqVZyNOka>OWF=qO=o)M@?Hpq9FXMw>*N{0MZE31kx1J3{o5N z7RuNH(h|}d(jL+Q!oPycS%Sx2Z$UUaT!FA|IV*As>r?Z_U7Ro&MScs9_!*!wT~blL(*^ysX9`)6ZshO1Y&`i0=WzK&yZgs zw;xQn`1JWD9V~__ShaiU`M<7Qb#~>#l??CoLIKyB@ z>1nBi40ZTw~Lp&iosQVpcJ!Bp783XAC=>ds_a2nz<=w*;KkhPE? z;WJ2e6yO5F1>*v0Fcs2Hv>2pzwDH#h(;#n(?Ss@{TtnOh*!a_wvxx7!C_h;3p29OZ zJm16Zdme7&P90Bf@r2(+$R)@J5FV6#8?q6y39=co29ge03*k9Co@wK`xqm`2-gg*2hAZ|10o~41@5895W%aApEsT03--~fb^5Z4+W+UL(aMnL96d>|QC@xc(aXv$uM`4Pesf;>{hHHT}= zNZ6Exybt~YkZczE+(5#9j)Uok1VeLKME#*^R5LR|9;~KNr_jwA{v_l$3`%=B~{HX*B>5ts?Fs#&EpXbUtGdl4DVjF`>BFlG}lEXB+i!!llzqaF=OF%zOk z&JuKy#%2H}#40mOXtp{1yMZ?|LYEHlfDrU92i1aD54z@b5ALl_Ci^NZu&%nW%mqjTWSIcw8q7KF33 zIYXN>Gvm!UE1NTNU$###NGzl~A!+@Ku>hnSexJnLGIXUuF(9nDX2horeS$ zLd z2z%-h$X|6*c1VU{4fKU@HD#O}Ip&b&P-ZQfb}2M4d-SWSekE+4L7qY$K>h{!9r6?8 zN62l+Ey(wf8<6XeYmnVY=u_aAkW9#DkWV02AeSJk*#76>I1AYbc^i@rSp!)OSr6F& zSqoVw^>={#A$uUZAe$gtAe$jOA=@EaA=@B3Ahh2N*$deRIRMFkoPr$T_&)*1A;@va zLC7)45eT`%65j|3F;j^v=Of@eINa!fIiS(AXWaQV|R1s1<5?z+D(p2*QrV z3^WHE(w3w@Qi3^02w~6dFywlo@=y=z~ z9Y99S%4Q{#=iL?WvdlX!`ZEmg0Xji=m&H9Z?yvFgihE_;8;cd5^VHJ$d85^OF23+7 z6XGWNmvk?m(PN%kM=gfkv(&T$sjV*r9Q#dGy7^bbKlB=LWr1qPW4a#!4%RU3ydLfx z>-mqS<)j_9r3#DE3srj%OO742)J;_;&TRXeqXGrLAKW+_h z1&t9&2|kJsPO4d_yko83a#c}3r`X?)!xc{ ztJ3_&|Jpn5T&ba|QV}07Tt3~z22Bl6 z28m2ft*lHG)bUipb`Qzfb3nF7R8J)4^!#dazC*F86 z=OZ88C#YH=?iTT^RBfR?Rm2~Q)k+a-5JednzL=JmV){ImiM09l?f#2;?(CqKzGgtR znih9)VTo!NW0$BU@L+~+hlYD#X8G2pnfX8aEY~1anFPiQjA4LhQt7MFF$RcVkE?}* z*Ge^)GE@9|6;!&2ItFU~=b%oC($_(KEp~sd+SP}-M2~OP;&@p43@RQ)rqeT2Tv-L! zD0~>YjZi)Uj1(vyYPU7T!vO5Cr?wV%pyE+i ztM|~HV?|gRwAo_id*~iQOn{b0pujY}XA|qw)CMY^FO3{x1pn zd`@Jmg&`}jzBMMj*o5f^?WFnZz%;RYovS*{_kC8mT=B-ysX-lG z8QmqG!LFA1g3!!%g=QbVQ|L>Vf%yv2N@2cJF8*Hgk;~^_#YD^Xs0H(-qeJ_(xHmlb z*k>*aKQUoFGErYF2RQiFQas5^U41k6j5B4mn=ZSN;xY_^%-5Xm+g~Wsed4{9E(7yL zs3$GMZ&qC5_l3*nYvH{Cahq>WZSc=akDu+YpYAfKB%)w|!vy_lXTEtg->7~*&QFi? z3PJX(bg>!+LFTJkH}>h%_VGujKXn=nI#ah1+|N$iC?hi_&6b}7K+ zQ$bYSjEbrArtz}3`rSOgUbAy@Uzb5wfqMK;YUCqPYzx+$>!SGVZKMU#yiQguYCXQZkOHLq7@8ko$@m-P||7_JX5Uetg$YG8`5X@z5DyRcWd0s z<@3x>tlEl^W4_JS_g10&w;C3|=dy?pSD1+y@gp-a4o`G>L9)1XVE?DVF&-|vm7?@E z*m;S0J78zN%y!xvM<+i%w)ksRDOp{A;+`5Hl4w^?WWi3X*`~I{!JMDBsR0i2{j%ed z&xSmWfBFmKHtO&_5wRT&Z@!TBi-CXa3*I@jzRSRTW$o<~h5JXhy?@x{Q%uZ4P>1=B z+lDo6j;P%@{bQFwlsLw?&6nc#nlbKp{n2e6y9~_N=GM&osE7CB^rWMX$!se#qaA6YfuO&hSV*XR=~*Njt@sbbhJ)yHAJrgwt>r$Y-@ z);ggaSPyZR}l2{I?ngVmq=Yjh-EV*9VYe-?m zJcje^X>mFbFbVT>NNO-E!kUP8;SjFkf*z;j&t>?Id-a z%g0?DW!&;5$*CnB4`zbC%M10gVgU@4p<*kcxOi`$+8w77%kM{%=M$?dx)pWD++0+|?uU0VF$Ul; zUwJ&W-IevnFHhvf7>5Fy{gBwXUu|eTjXxC>DK0Nni3?Z=4+I@eU?$fuigC* z^eiHic#J3fGcYJsoCZXiH-&NlNtkb2ezJ7WN2k(fK2epLnBg#tEd#}#42)XC4#$rj z2ToBFOH zV7`oP%ujtUO)vOkS(lH$h&Y58!o}c2sD`Fu7QoRF5%J#iUcZ(L%S`+BxXV0F9D_kn zG7NCwDD~*YeT_WQ8g+3QOoWdYe1^BHVlDr{l(~hK$cP%%LQow^!t*fVUI>H2FlgAY z$?UO(-Qx`dZi~Dv>cRjA!n@G!L)dX!VeNwfHQdjYNOC1{Q_O=w&=VNo8ZNbIe#^qv z6?PAn5uqZBMCm7sQfD4J==J4S-%g2~)hgK$nXkmox8=hRYrC(A*E^tpAX1n2%|?8Vhj<)b5G8n2A^x9(M>n$Yib5Y2 z7X3yEH=6I_CSAYDE|eSFF(|u{Z!AR1k$vNqzEEx>jDEjvDn?}(clP8s=NZ{|oJ6WP z=$5*1U#~i+-uM6)oo1@~)_}anFC@kvQ{T!i(qGn%elOPWW!3TmXKYQ#vdL^3IWumr z?YRFde**V^W)1((lE$w~*wA{zyqJTRq-xw>zkZrB#|a|+V{8b_J@p?{)yuNHugL+0OXs$_3{$R6%0PZ`l;g<3GX=Drne z&!_=$e{PG&2>)7~8D88##E3IC5QARaIrLJRMjKguaeq+y=ri+@v#LkXOOp%1)?1BN z1V#CO5)o%{O~toR;9O|x1~K?7E=kRoy_bLcTCqIKhHaE<0*2H)aqKK6BJ-v1wkD2F zzqbtJ%|90nTwodRg~uDTBaZ(Nxuo4*-G_6bMUUH#U9^hlkm3~_b>sfjSkdJiZs?zM z7Q@e}l}c+}jY0nBvgkf~VX@8h={a1GnXfs&KYu{EN59VE4H3pRFUabP0_QQ{+lm7I zZgz+HCVAVCUL~u0=HZ5LV6{NnjS}5yw@{2aueNZQZ=nC~r$Z%p_x&kygh zZ(Aqn6O0EK!uukUxC?^ph>uHlOGQdIdKVQ7}+?i2fI`d?$znm(`-BP@(Vk z)=yif=3cSjV!28G!0xu57^*N>++mbcQR_$6cd{4|f7ytdM} zy73fDIBD>D5e49PP$yuHTR0|<-+3^0Q=2OMF6;Fl!>qlc68!@$dbP0&$E{~@&7Cee1_~F<`kkn87e;bTE9=wA85Vy4Df%|H=nB} zN5^5tRrzsu<;?YAeZJrlj8#p)GYiaK<+i_-d{xh15-;DF%Iyo!}o}Q2`dehAsOr-0JtSD;_T~As^DKE{=YWOmq+n z5Fwt$|4L1WytlCBw;O+;lK15;PuqU%_`Nrt?jRF-^yf#3I=9r~>i46>buYK#j$EU0 z&K#C~FRVPB>+5&!x{~o-9HkfT@-xKSLYzRn*yoG##0Bh31mgZdow)ZdcZvP1m@E2K zBDMfR?Gs}FB~QT42X?=oZoO!D*TdbgsN&M9ntu(;H={+j+bCt;F~&7!xieks$GWZW zFD*jx$G*ja+eop#*h=Usb~4^1aSd9~6oe{)cu%KBTsv21-#0Q8E)-Y8hr1@_+dl9b zy!qKMm(O-l^#@$ne=tToEr4?0h9PUHaGugZU8~HmEe&f1SQO!35J}lZbArFviC`#y z3bj6B@MCCW#1oS9#R4~wD~11$&~^%?kegjSIaZV`-Al7#XTv=kBJ4Q-*g@(|iO(ds8G<9{FyW_o*< zq}cc6? z!SQ;d=*LU!!uvjAFp8L?wo^p^`^be6v*Xn<>piikdq~dA zFm`0qMBPU_P8Yk}(XCwB4aAS!_14N+*> zm-B)_$vH#CV3_?FGhXCeW9BaqySVqowguLkG1ZXm<2VNcJNi?t{d>+0nYOTKt6hymiQXL`bz9r=*VhYQnltBJ8Zn6s%~T>;WU)}1jY zE?(tULh>?*Os9&d%OzKDW=y%72v^Xro}ZSD`BeQ(6YCYXx8yYVV$aB~KiP{MeJ2>3 zThr&a-m&21Y!BHyxRR6I#3+c7P4S!R7AV(Tna>xpB+Hhw64VbDi}^NW_xU2d5b(LO zsOPgsj=o0ZTpZ+3{BtwdUkD{GTC;2OxhgkOmhGCe8J=qsQ+eLn=!@#DrQ%3#6e(vp z%^H_YmNQ)TVkhhLCEeC2PtHIx1KHL0vUtrR3$HvVYkyHW4|>TgG0*|nCb~lly0{EK zyu|Rz^WnK#w&QViUDqzZz^4p+DmXTzR_oQgm+Mli87>3# zYb)cT%JJ%}ssEo{Gs)icd}aP!i~r%3r%{Wq=&ZbvdC6$4C6chX*PfH zwG|FboA>M1RhfI%8U3o!DCe}lN8oGRwa5|p+%oWr z`Zw2#=O*d@>)H9=^y=sGEGI~L*C$IL7o+D^X5&8Xg{vHCFK7M##-QN!n%GeaYuYMN zx*9I+zA1$Zj~${|X}19NK)QIVH10T0q>EKm-HMCxrQHJ6i|L{y6!p_|(HadBp4u{FMSPU@4f33grVE!e>+gmIsKj z3}Vc+)_6qc*qm~0$M^d%M)4F&f0pe9pF)VS)x%T!j=1;aX<6x0POL10VT(ifI~NY7#dF5~&auh#t;_;2>V|Vdh z**m^^a(eX@+-x*e$EZ(Q#N0c713vP(+bWTTppLCDU=B8odO9#*jRObRSP{%Zuqf~* z3_5Qy3?4ih6MwSPq7AB29}H``pV(dA&E9$jRBn^>jy`pARsJA)lLuc(iNmOH z!s5p%>|_1J4de@l4IdM9t*90E&gZs@8s*)J*ZO#?k-cpL{r4u`?X?OaW$%3T;UlCv z9%1pU3uP{xORqYkcu400N{Mt>SoPf2B^A(Lci=Awu*}hCYPI>H*~6cZ1`f+Hcb1(- z*>60(w8J;hb18h}L%1TMMFq4~H5hQcdRQQ?|I@%r#*}D)QJ1Xcfaoy!~-qCXcyFfb>XDJgbbt6D^+ju$Ad0~@975mu-AU#@nhgp2r~(T|H5?G754nUG%Js;6udD%BX+$ zqsDXJ-m~|2Ncd_MHYxSw(R8n#)`o~DmE9^U?Sx+yx6iZZNI5N*+<_eALA&ktEcM-t zIh(5@Mm9NeASXl`qA{;KUOjcnIkRsyx6+P$ z&5Yb9&uH>=?}j(W>bchkn%+4A9Dc`*9=&ej*?ITn6c4Fbvd; z<6?Gox8fx)z>vRtv+nOZIB-n2g)YMz$Hj42;P2>VR>u~BTxZJFa0|wsdY2kVq{@3n z{<=3=b8&L9Q>9Tt3~-L|I$|CSg4V!*1Fz+~?>L92JoSSCPRp=}8SvpOROF4OoqNT9 z&Yj&rEf6Q7B&lm;%e#y{#>CWGAW*Nz#vT9iJitheM%KNNg zQ0uLW`_mWh9qlrx4j(RbHR>(vo{=lR>hh^4wj(G`Tto$8Zqj3m7dJ?a5sv|mc?ik| zDE{b?f1CGf#kqp66V+?F*=uD;gD)rbY2?4;>Is*@1?f|%pl^UO>)Ujf&$Y8+d`%ST zzO>N7ek#{*&a&w)i`-&2BF7)KyZ~_2IA^p_o5&9B>7qq|TSMER%f_6tTPzR2Jovq=<%EObn*wts{X@6M z4-{)!9-a{&24H^9i%hfQow&JRQ=wvw|K+lHX#?nCs|)Wyl%pPkvK`iL9^db-NBNIj zLG=c(ihVw{eBrY8{usOWQ=x z$c3}wZXhqSWZ0fX|FGpRUHErb*q6469#(IY!UDh1v^pH9RxZP<95oJoe7jd?SNIxY zU=UiQk*s&mVhwJu+A;jB%iyJLr6({%97RydYAxiPo}{qKYYg3tXBGDA-A&dL7(7| z)Caho%hRS>ZZ(7ce4^cqJn0K#RQwng8+*QHOBx`5e8NK(kRY?>Ye9Q*cX9Y(eXGsAeMw;c|Ns9ydUaT3iH$L zP`A-J*9xu6Y^fGiltF!#{$<6mKN7T3;YUF2KiHvV*yM3i(Tnu+B zJ1nEpJ8q8?@?7U1Uc={?5~6xZcORT{Em5R=feGc>UYvgT?T?C&xKgEz!?0-Q5dBKJ H|M33+OZ~aM delta 44705 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 mRLH$Vp+fn} Date: Sun, 10 Nov 2024 14:56:24 +0100 Subject: [PATCH 34/93] feat: add skills to seeding --- src/server/db/seed.ts | 67 ++++++++++++++++++++++++++++++++-- src/server/db/tables/skills.ts | 4 ++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index 854a0639..02d1ce17 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -1,4 +1,12 @@ -import { type InsertUser, locales, users } from '@/server/db/tables'; +import { + type InsertSkill, + type InsertUser, + type InsertUserSkill, + locales, + skills, + users, + usersSkills, +} from '@/server/db/tables'; import { fakerEN, fakerNB_NO, fakerSV } from '@faker-js/faker'; import { routing } from '@/lib/locale'; @@ -24,14 +32,67 @@ const insertedLocales = await db .returning(); console.log('Locales inserted:', insertedLocales); +console.log('Inserting user...'); const user: InsertUser = { name: 'Frank Sinatra', username: 'fransin', passwordHash: await hashPassword('Password1!'), }; -console.log('Inserting user...'); -await db.insert(users).values(user); +const insertedUser = await db.insert(users).values(user).returning(); console.log('User inserted'); +console.log('Inserting skills...'); +const skillsdata: InsertSkill[] = [ + { + identifier: 'printing', + }, + { + identifier: 'unix', + }, + { + identifier: 'raspberry', + }, + { + identifier: 'laser', + }, + { + identifier: 'arduino', + }, + { + identifier: 'souldering', + }, + { + identifier: 'workshop', + }, +]; +const insertedSkills = await db.insert(skills).values(skillsdata).returning(); +console.log('Skills inserted'); + +if (insertedUser.length === 0 || insertedSkills.length < 5) { + console.error('Error: Inserted user or skills data is incomplete.'); + process.exit(1); +} + +console.log('Inserting userskills...'); +const usersSkillsData: InsertUserSkill[] = [ + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[0]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[1]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[2]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[4]?.id ?? 0, + }, +]; +await db.insert(usersSkills).values(usersSkillsData); + process.exit(); diff --git a/src/server/db/tables/skills.ts b/src/server/db/tables/skills.ts index dcac4063..86df66ef 100644 --- a/src/server/db/tables/skills.ts +++ b/src/server/db/tables/skills.ts @@ -46,6 +46,8 @@ const usersSkillsRelations = relations(usersSkills, ({ one }) => ({ type SelectSkill = InferSelectModel; type InsertSkill = InferInsertModel; +type SelectUserSkill = InferSelectModel; +type InsertUserSkill = InferInsertModel; export { skills, @@ -54,4 +56,6 @@ export { usersSkillsRelations, type SelectSkill, type InsertSkill, + type SelectUserSkill, + type InsertUserSkill, }; From 998660a8eef8aa6cf01080aba60b4d12dd8bc0ea Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 10 Nov 2024 15:26:49 +0100 Subject: [PATCH 35/93] chore: fixup docker locally --- compose.local.yml | 2 -- package.json | 8 ++++---- src/server/db/seed.ts | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compose.local.yml b/compose.local.yml index 7025a25a..a62c0542 100644 --- a/compose.local.yml +++ b/compose.local.yml @@ -10,7 +10,6 @@ services: - ./data/db:/var/lib/postgresql/data ports: - 5432:5432 - user: '501:20' s3: image: bitnami/minio:2024 restart: unless-stopped @@ -23,4 +22,3 @@ services: ports: - 9000:9000 - 9001:9001 - user: '501:20' diff --git a/package.json b/package.json index f04b1ee5..6f14c078 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,15 @@ "build": "next build", "postbuild": "mkdir -p .next/standalone/public .next/standalone/.next/static && cp -r public/* .next/standalone/public && cp -r .next/static/* .next/standalone/.next/static", "start": "drizzle-kit migrate && bun run .next/standalone/server.js", - "db:start": "docker-compose -f compose.local.yml up db -d", - "db:stop": "docker-compose -f compose.local.yml down db", + "db:start": "docker compose -f compose.local.yml up db -d", + "db:stop": "docker compose -f compose.local.yml down db", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "db:seed": "bun run src/server/db/seed.ts", - "s3:start": "docker-compose -f compose.local.yml up s3 -d", - "s3:stop": "docker-compose -f compose.local.yml down s3" + "s3:start": "docker compose -f compose.local.yml up s3 -d", + "s3:stop": "docker compose -f compose.local.yml down s3" }, "dependencies": { "@aws-sdk/client-s3": "^3.679.0", diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index 02d1ce17..e86217a0 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -94,5 +94,6 @@ const usersSkillsData: InsertUserSkill[] = [ }, ]; await db.insert(usersSkills).values(usersSkillsData); +console.log('Userskills inserted'); process.exit(); From b36724fe9171678f37c204fbb35d496da9cc9b61 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 10 Nov 2024 16:29:37 +0100 Subject: [PATCH 36/93] feat: add default toast on API error --- bun.lockb | Bin 269284 -> 269724 bytes package.json | 1 + src/app/[locale]/layout.tsx | 2 ++ src/components/ui/Toaster.tsx | 49 ++++++++++++++++++++++++++++++++ src/lib/api/error.ts | 27 ++++++++++++++++++ src/lib/api/queryClient.ts | 10 +++++++ src/lib/api/types.ts | 8 ++++++ src/server/api/index.ts | 3 +- src/server/api/routers/auth.ts | 26 +++++++++++++++++ src/server/api/routers/index.ts | 2 ++ 10 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/components/ui/Toaster.tsx create mode 100644 src/lib/api/error.ts create mode 100644 src/lib/api/types.ts create mode 100644 src/server/api/routers/auth.ts create mode 100644 src/server/api/routers/index.ts diff --git a/bun.lockb b/bun.lockb index 3232640ee72dbdecfa40277eebbe2fdf4edcc6d3..0f9617aa8a124b9ed35149814756ceca461ca464 100755 GIT binary patch delta 42055 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 B;u-(| delta 41465 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 diff --git a/package.json b/package.json index 6f14c078..57fa8829 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "react-day-picker": "^9.2.1", "react-dom": "^19.0.0-rc-fb9a90fa48-20240614", "reading-time": "^1.5.0", + "sonner": "^1.7.0", "superjson": "^2.2.1", "tailwind-merge": "^2.5.4", "tsparticles": "^3.5.0", diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index ac7f8cd4..b3310ba1 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,4 +1,5 @@ import { RootProviders } from '@/components/providers/RootProviders'; +import { Toaster } from '@/components/ui/Toaster'; import { routing } from '@/lib/locale'; import { cx } from '@/lib/utils'; import { getTranslations, setRequestLocale } from 'next-intl/server'; @@ -84,6 +85,7 @@ export default async function LocaleLayout(props: LocaleLayoutProps) {
    {children} +
    diff --git a/src/components/ui/Toaster.tsx b/src/components/ui/Toaster.tsx new file mode 100644 index 00000000..c40a780f --- /dev/null +++ b/src/components/ui/Toaster.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { + CircleCheckIcon, + CircleXIcon, + InfoIcon, + LoaderIcon, + TriangleAlertIcon, +} from 'lucide-react'; +import { useTheme } from 'next-themes'; +import { Toaster as Sonner, toast } from 'sonner'; + +import { useMediaQuery } from '@/lib/hooks/useMediaQuery'; + +type ToasterProps = React.ComponentProps; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = 'system' } = useTheme(); + const isDesktop = useMediaQuery('(min-width: 768px)'); + + return ( + , + info: , + warning: , + error: , + loading: , + }} + {...props} + /> + ); +}; + +export { Toaster, toast }; diff --git a/src/lib/api/error.ts b/src/lib/api/error.ts new file mode 100644 index 00000000..295df0b0 --- /dev/null +++ b/src/lib/api/error.ts @@ -0,0 +1,27 @@ +import { toast } from '@/components/ui/Toaster'; +import type { TRPCClientError } from '@/lib/api/types'; + +function handleGlobalError(error: Error) { + const TRPCError = error as TRPCClientError; + + if (TRPCError.toast) { + switch (TRPCError.toast) { + case 'success': + toast.success(TRPCError.message, { richColors: true }); + break; + case 'info': + toast.info(TRPCError.message, { richColors: true }); + break; + case 'warning': + toast.warning(TRPCError.message, { richColors: true }); + break; + default: + toast.error(TRPCError.message, { richColors: true }); + break; + } + } else { + throw error; + } +} + +export { handleGlobalError }; diff --git a/src/lib/api/queryClient.ts b/src/lib/api/queryClient.ts index b121db51..7c4b2655 100644 --- a/src/lib/api/queryClient.ts +++ b/src/lib/api/queryClient.ts @@ -1,11 +1,21 @@ import { + MutationCache, + QueryCache, QueryClient, defaultShouldDehydrateQuery, } from '@tanstack/react-query'; import SuperJSON from 'superjson'; +import { handleGlobalError } from '@/lib/api/error'; + function createQueryClient() { return new QueryClient({ + queryCache: new QueryCache({ + onError: handleGlobalError, + }), + mutationCache: new MutationCache({ + onError: handleGlobalError, + }), defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts new file mode 100644 index 00000000..887c177d --- /dev/null +++ b/src/lib/api/types.ts @@ -0,0 +1,8 @@ +import type { router } from '@/server/api'; +import type { TRPCClientError as BaseTRPCClientError } from '@trpc/client'; + +type TRPCClientError = BaseTRPCClientError & { + toast?: 'success' | 'info' | 'warning' | 'error'; +}; + +export type { TRPCClientError }; diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 3e770ca6..7f50e5ec 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -1,8 +1,9 @@ -import { testRouter } from '@/server/api/routers/test'; +import { authRouter, testRouter } from '@/server/api/routers'; import { createCallerFactory, createRouter } from '@/server/api/trpc'; const router = createRouter({ test: testRouter, + auth: authRouter, }); const createCaller = createCallerFactory(router); diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts new file mode 100644 index 00000000..b9305dbb --- /dev/null +++ b/src/server/api/routers/auth.ts @@ -0,0 +1,26 @@ +import { publicProcedure } from '@/server/api/procedures'; +import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket'; +import { createRouter } from '@/server/api/trpc'; +import { getFeideAuthorizationURL } from '@/server/auth/feide'; + +import { TRPCError } from '@trpc/server'; +import { headers } from 'next/headers'; + +const ipBucket = new RefillingTokenBucket(5, 60); + +const authRouter = createRouter({ + getFeideAuthorizationURL: publicProcedure.query(async () => { + const headerStore = await headers(); + const clientIP = headerStore.get('X-Forwarded-For'); + + if (clientIP !== null && !ipBucket.check(clientIP, 1)) { + throw new TRPCError({ + code: 'TOO_MANY_REQUESTS', + message: 'Rate limit exceeded. Please try again later.', + }); + } + return getFeideAuthorizationURL(); + }), +}); + +export { authRouter }; diff --git a/src/server/api/routers/index.ts b/src/server/api/routers/index.ts new file mode 100644 index 00000000..10c25904 --- /dev/null +++ b/src/server/api/routers/index.ts @@ -0,0 +1,2 @@ +export * from './test'; +export * from './auth'; From 4b962e3da77351f4d967d366542fe8909af47cdf Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:00:15 +0100 Subject: [PATCH 37/93] feat: create rpc toast error type --- messages/en.json | 3 ++- messages/no.json | 3 ++- src/server/api/locale.ts | 20 ++++++++++++++++++++ src/server/api/routers/auth.ts | 3 +++ src/server/api/trpc.ts | 7 ++++++- src/server/api/types.ts | 7 +++++++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/server/api/locale.ts create mode 100644 src/server/api/types.ts diff --git a/messages/en.json b/messages/en.json index 3c0eb10e..a1bd925c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -31,7 +31,8 @@ "error": "Oops! Something went wrong", "errorDescription": "Don't worry, our best hackers are on it!", "goToHomepage": "Return to homepage", - "tryAgain": "Try again" + "tryAgain": "Try again", + "invalidLocale": "Invalid locale: {locale}" }, "auth": { "welcome": "Welcome!", diff --git a/messages/no.json b/messages/no.json index 57fa7911..dffe67fa 100644 --- a/messages/no.json +++ b/messages/no.json @@ -31,7 +31,8 @@ "error": "Oops! Noe gikk galt", "errorDescription": "Ikke bekymre deg, våre beste hackere jobber med saken!", "goToHomepage": "Gå tilbake til hjemmesiden", - "tryAgain": "Prøv igjen" + "tryAgain": "Prøv igjen", + "invalidLocale": "Ugyldig språk: {locale}" }, "auth": { "welcome": "Velkommen!", diff --git a/src/server/api/locale.ts b/src/server/api/locale.ts new file mode 100644 index 00000000..8c612520 --- /dev/null +++ b/src/server/api/locale.ts @@ -0,0 +1,20 @@ +import { TRPCError } from '@trpc/server'; +import { getTranslations } from 'next-intl/server'; +import { cookies } from 'next/headers'; + +import { routing } from '@/lib/locale'; + +async function getLocaleFromCookie() { + const cookieStore = await cookies(); + const locale = cookieStore.get('locale')?.value ?? routing.defaultLocale; + if (!routing.locales.includes(locale as 'en')) { + const t = await getTranslations('error'); + throw new TRPCError({ + code: 'BAD_REQUEST', + message: t('invalidLocale', { locale }), + }); + } + return locale; +} + +export { getLocaleFromCookie }; diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index b9305dbb..9d3f7362 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -17,6 +17,9 @@ const authRouter = createRouter({ throw new TRPCError({ code: 'TOO_MANY_REQUESTS', message: 'Rate limit exceeded. Please try again later.', + data: { + toast: 'error', + }, }); } return getFeideAuthorizationURL(); diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 9149adcb..a1b62d57 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -1,4 +1,5 @@ import type { createContext } from '@/server/api/context'; +import type { TRPCError } from '@/server/api/types'; import { initTRPC } from '@trpc/server'; import superjson from 'superjson'; import { ZodError } from 'zod'; @@ -6,12 +7,16 @@ import { ZodError } from 'zod'; const trpc = initTRPC.context().create({ transformer: superjson, errorFormatter({ shape, error }) { + const TRPCError = error as TRPCError; return { ...shape, data: { ...shape.data, zodError: - error.cause instanceof ZodError ? error.cause.flatten() : null, + TRPCError.cause instanceof ZodError + ? TRPCError.cause.flatten() + : null, + toast: TRPCError.toast, }, }; }, diff --git a/src/server/api/types.ts b/src/server/api/types.ts new file mode 100644 index 00000000..758a6807 --- /dev/null +++ b/src/server/api/types.ts @@ -0,0 +1,7 @@ +import type { TRPCError as BaseTRPCError } from '@trpc/server'; + +type TRPCError = BaseTRPCError & { + toast?: 'success' | 'info' | 'warning' | 'error'; +}; + +export type { TRPCError }; From 029fa38162d4af7fea8ac1989b5a72c4c436199e Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:35:11 +0100 Subject: [PATCH 38/93] chore: work on getting locale onthe backend through headers --- bun.lockb | Bin 269724 -> 269724 bytes messages/en.json | 3 +- messages/no.json | 3 +- src/app/[locale]/auth/page.tsx | 16 +++++++--- src/app/api/auth/feide/route.ts | 1 + src/app/api/data/[trpc]/route.ts | 6 ++-- .../assets/logos/HackerspaceLogo.tsx | 2 +- src/components/providers/TRPCProvider.tsx | 3 ++ src/lib/api/server.ts | 3 +- src/server/api/context.ts | 11 ++++++- src/server/api/locale.ts | 29 +++++++++--------- src/server/api/routers/auth.ts | 7 +++-- src/server/api/trpc.ts | 4 +-- src/server/api/types.ts | 7 ----- src/server/auth/feide.ts | 4 +-- 15 files changed, 56 insertions(+), 43 deletions(-) delete mode 100644 src/server/api/types.ts diff --git a/bun.lockb b/bun.lockb index 0f9617aa8a124b9ed35149814756ceca461ca464..30addd66e0b32bee4c7a154c64526a77374aecd1 100755 GIT binary patch delta 31 ncmbQUTVT#^frb{wEllN8*%{*u_007Q+Uuq=ZLgckEY|@5xjG8} delta 31 jcmbQUTVT#^frb{wEllN8*_jx?puKJ?)AqWl%yJz7rv(YF diff --git a/messages/en.json b/messages/en.json index a1bd925c..3c0eb10e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -31,8 +31,7 @@ "error": "Oops! Something went wrong", "errorDescription": "Don't worry, our best hackers are on it!", "goToHomepage": "Return to homepage", - "tryAgain": "Try again", - "invalidLocale": "Invalid locale: {locale}" + "tryAgain": "Try again" }, "auth": { "welcome": "Welcome!", diff --git a/messages/no.json b/messages/no.json index dffe67fa..57fa7911 100644 --- a/messages/no.json +++ b/messages/no.json @@ -31,8 +31,7 @@ "error": "Oops! Noe gikk galt", "errorDescription": "Ikke bekymre deg, våre beste hackere jobber med saken!", "goToHomepage": "Gå tilbake til hjemmesiden", - "tryAgain": "Prøv igjen", - "invalidLocale": "Ugyldig språk: {locale}" + "tryAgain": "Prøv igjen" }, "auth": { "welcome": "Velkommen!", diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index e64c7e31..dd2b79a6 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -1,9 +1,11 @@ import { FeideLogo } from '@/components/assets/logos/FeideLogo'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; +import { api } from '@/lib/api/server'; import { Link } from '@/lib/locale/navigation'; import { FingerprintIcon } from 'lucide-react'; import { getTranslations, setRequestLocale } from 'next-intl/server'; +import ExternalLink from 'next/link'; export default async function SignInPage({ params, @@ -13,17 +15,23 @@ export default async function SignInPage({ const { locale } = await params; setRequestLocale(locale); const t = await getTranslations('auth'); + const feideUrlHref = await api.auth.getFeideUrlHref(); return ( -
    +

    {t('welcome')}

    {t('description')}

    -
    +

    {t('signInWith')}

    - + + ); +} + +export { FeideButton }; diff --git a/src/components/providers/TRPCProvider.tsx b/src/components/providers/TRPCProvider.tsx index f7761f19..4011ee4e 100644 --- a/src/components/providers/TRPCProvider.tsx +++ b/src/components/providers/TRPCProvider.tsx @@ -4,7 +4,7 @@ import { env } from '@/env'; import { api } from '@/lib/api/client'; import { createQueryClient } from '@/lib/api/queryClient'; import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client'; +import { httpBatchLink, loggerLink } from '@trpc/client'; import { useState } from 'react'; import SuperJSON from 'superjson'; @@ -32,14 +32,9 @@ function TRPCProvider(props: { children: React.ReactNode }) { process.env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), - unstable_httpBatchStreamLink({ + httpBatchLink({ transformer: SuperJSON, url: `${env.NEXT_PUBLIC_SITE_URL}/api/data`, - headers: () => { - const headers = new Headers(); - headers.set('x-trpc-source', 'nextjs-react'); - return headers; - }, }), ], }), diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index f326d5a5..a6a0f10c 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,15 +1,16 @@ +import { env } from '@/env'; import { publicProcedure } from '@/server/api/procedures'; import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket'; import { createRouter } from '@/server/api/trpc'; -import { getFeideAuthorizationUrl } from '@/server/auth/feide'; +import { createFeideAuthorization } from '@/server/auth/feide'; import { TRPCError } from '@trpc/server'; -import { headers } from 'next/headers'; +import { cookies, headers } from 'next/headers'; const ipBucket = new RefillingTokenBucket(5, 60); const authRouter = createRouter({ - getFeideUrlHref: publicProcedure.query(async () => { + signInFeide: publicProcedure.mutation(async () => { const headerStore = await headers(); const clientIP = headerStore.get('X-Forwarded-For'); @@ -19,8 +20,27 @@ const authRouter = createRouter({ message: 'Rate limit exceeded. Please try again later.', }); } - const feideUrl = await getFeideAuthorizationUrl(); - return feideUrl.href; + + const cookieStore = await cookies(); + const { state, codeVerifier, url } = await createFeideAuthorization(); + cookieStore.set('feide-state', state, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); + cookieStore.set('feide-code-verifier', codeVerifier, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); + + console.log(cookieStore.getAll()); + + return url.href; }), }); diff --git a/src/server/auth/feide.ts b/src/server/auth/feide.ts index a7d76d75..b4fa2cc5 100644 --- a/src/server/auth/feide.ts +++ b/src/server/auth/feide.ts @@ -15,21 +15,22 @@ const feideOAuthClient = new OAuth2Client( }, ); -function getFeideAuthorizationUrl() { +async function createFeideAuthorization() { const state = generateState(); - const codeVerifier = generateCodeVerifier(); // Optional for PKCE flow - - return feideOAuthClient.createAuthorizationURL({ + const codeVerifier = generateCodeVerifier(); + const url = await feideOAuthClient.createAuthorizationURL({ state, scopes: ['openid', 'profile', 'email'], codeVerifier, }); + return { + state, + codeVerifier, + url, + }; } -async function validateFeideAuthorizationCode( - code: string, - codeVerifier: string, -) { +async function validateFeideAuthorization(code: string, codeVerifier: string) { try { const tokens = await feideOAuthClient.validateAuthorizationCode(code, { codeVerifier, @@ -43,9 +44,10 @@ async function validateFeideAuthorizationCode( } catch (error) { if (error instanceof OAuth2RequestError) { // probably invalid credentials etc + // will be handled by returning null } console.error(error); } } -export { getFeideAuthorizationUrl, validateFeideAuthorizationCode }; +export { createFeideAuthorization, validateFeideAuthorization }; From 095df5bb6bb1ee198b223291f5dc36b41922e474 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:50:37 +0100 Subject: [PATCH 42/93] fix: feide route --- src/app/api/auth/feide/route.ts | 12 +++--------- src/components/auth/FeideButton.tsx | 3 ++- src/server/api/routers/auth.ts | 2 -- src/server/auth/feide.ts | 11 ++++++++++- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index bcf74a10..d1fe0045 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -11,33 +11,27 @@ export async function GET(request: NextRequest) { const cookieStore = await cookies(); const storedState = cookieStore.get('feide-state')?.value; const codeVerifier = cookieStore.get('feide-code-verifier')?.value; - console.log('step 1'); - console.log(code, state, storedState, codeVerifier); if (!code || !state || !storedState || !codeVerifier) { return NextResponse.json(null, { status: 400 }); } - console.log('step 2'); if (state !== storedState) { return NextResponse.json(null, { status: 403 }); } - console.log('step 3'); const tokens = await validateFeideAuthorization(code, codeVerifier); if (!tokens) { return NextResponse.json(null, { status: 500 }); } - console.log('step 4'); const userInfoResponse = await fetch(env.FEIDE_USERINFO_ENDPOINT, { headers: { Authorization: `Bearer ${tokens.accessToken}`, }, }); - console.log('step 5'); const userInfo = await userInfoResponse.json(); console.log(userInfo); - return new Response(null, { - status: 500, - }); + + cookieStore.delete('feide-state'); + cookieStore.delete('feide-code-verifier'); } diff --git a/src/components/auth/FeideButton.tsx b/src/components/auth/FeideButton.tsx index de5d9c1e..937d5c58 100644 --- a/src/components/auth/FeideButton.tsx +++ b/src/components/auth/FeideButton.tsx @@ -1,6 +1,7 @@ 'use client'; import { FeideLogo } from '@/components/assets/logos/FeideLogo'; import { Button } from '@/components/ui/Button'; +import { Spinner } from '@/components/ui/Spinner'; import { api } from '@/lib/api/client'; import { useRouter } from 'next/navigation'; @@ -17,7 +18,7 @@ function FeideButton() { className='w-full bg-[#3FACC2]/90 hover:bg-[#3FACC2] dark:bg-[#222832] hover:dark:bg-[#222832]/40' onClick={() => signInMutation.mutate()} > - + {signInMutation.isPending ? : } ); } diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index a6a0f10c..3816f518 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -38,8 +38,6 @@ const authRouter = createRouter({ secure: env.NODE_ENV === 'production', }); - console.log(cookieStore.getAll()); - return url.href; }), }); diff --git a/src/server/auth/feide.ts b/src/server/auth/feide.ts index b4fa2cc5..5fed75b3 100644 --- a/src/server/auth/feide.ts +++ b/src/server/auth/feide.ts @@ -20,7 +20,16 @@ async function createFeideAuthorization() { const codeVerifier = generateCodeVerifier(); const url = await feideOAuthClient.createAuthorizationURL({ state, - scopes: ['openid', 'profile', 'email'], + scopes: [ + 'openid', + 'profile', + 'userinfo-name', + 'userid-feide', + 'email', + 'userinfo-mobile', + 'userinfo-photo', + 'userinfo-birthdate', + ], codeVerifier, }); return { From 5de3f582502d44b7d721e3abd411a23c46b101a7 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:23:28 +0100 Subject: [PATCH 43/93] chore: remove duplicate --- tailwind.config.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index f4720678..59add9b7 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -79,9 +79,6 @@ const config: Config = { }, }, plugins: [ - tailwindRadix({ - variantPrefix: false, - }), tailwindFluid, tailwindAnimate, tailwindScrollbar({ nocompatible: true }), From 195b02279e9a4406e235576d449d1fd4b1d21357 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:31:57 +0100 Subject: [PATCH 44/93] feat: update env var names --- .env.example | 20 ++++++++++---------- bun.lockb | Bin 269724 -> 269724 bytes compose.local.yml | 12 ++++++------ compose.yml | 12 ++++++------ drizzle.config.ts | 2 +- src/env.ts | 40 ++++++++++++++++++++-------------------- src/server/db/index.ts | 2 +- src/server/s3/index.ts | 8 ++++---- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.env.example b/.env.example index d0b55912..5c1c1285 100644 --- a/.env.example +++ b/.env.example @@ -17,18 +17,18 @@ NEXT_TELEMETRY_DISABLED="true" NEXT_PUBLIC_SITE_URL="http://localhost:3000" # Database -DATABASE_HOST="localhost" -DATABASE_PORT="5432" -DATABASE_USER="user" -DATABASE_PASSWORD="password" -DATABASE_NAME="database" +DB_HOST="localhost" +DB_PORT="5432" +DB_USER="user" +DB_PASSWORD="password" +DB_NAME="database" # Storage -STORAGE_HOST="localhost" -STORAGE_PORT="9000" -STORAGE_USER="user" -STORAGE_PASSWORD="password" -STORAGE_NAME="images" +S3_HOST="localhost" +S3_PORT="9000" +S3_USER="user" +S3_PASSWORD="password" +S3_BUCKETS="images" # Auth FEIDE_CLIENT_ID=" " diff --git a/bun.lockb b/bun.lockb index 0f9617aa8a124b9ed35149814756ceca461ca464..30addd66e0b32bee4c7a154c64526a77374aecd1 100755 GIT binary patch delta 31 ncmbQUTVT#^frb{wEllN8*%{*u_007Q+Uuq=ZLgckEY|@5xjG8} delta 31 jcmbQUTVT#^frb{wEllN8*_jx?puKJ?)AqWl%yJz7rv(YF diff --git a/compose.local.yml b/compose.local.yml index a62c0542..9405755f 100644 --- a/compose.local.yml +++ b/compose.local.yml @@ -3,9 +3,9 @@ services: image: postgres:16 restart: unless-stopped environment: - POSTGRES_USER: ${DATABASE_USER} - POSTGRES_PASSWORD: ${DATABASE_PASSWORD} - POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} volumes: - ./data/db:/var/lib/postgresql/data ports: @@ -14,9 +14,9 @@ services: image: bitnami/minio:2024 restart: unless-stopped environment: - MINIO_ROOT_USER: ${STORAGE_USER} - MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD} - MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME} + MINIO_ROOT_USER: ${S3_USER} + MINIO_ROOT_PASSWORD: ${S3_PASSWORD} + MINIO_DEFAULT_BUCKETS: ${S3_NAME} volumes: - ./data/s3:/bitnami/minio/data ports: diff --git a/compose.yml b/compose.yml index df300fe2..3d5fac3e 100644 --- a/compose.yml +++ b/compose.yml @@ -11,9 +11,9 @@ services: image: postgres:16 restart: unless-stopped environment: - POSTGRES_USER: ${DATABASE_USER} - POSTGRES_PASSWORD: ${DATABASE_PASSWORD} - POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} volumes: - ./data/db:/var/lib/postgresql/data networks: @@ -22,9 +22,9 @@ services: image: bitnami/minio:2024 restart: unless-stopped environment: - MINIO_ROOT_USER: ${STORAGE_USER} - MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD} - MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME} + MINIO_ROOT_USER: ${S3_USER} + MINIO_ROOT_PASSWORD: ${S3_PASSWORD} + MINIO_DEFAULT_BUCKETS: ${S3_NAME} volumes: - ./data/s3:/bitnami/minio/data networks: diff --git a/drizzle.config.ts b/drizzle.config.ts index 8436562c..da0da497 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -6,7 +6,7 @@ const config = defineConfig({ schema: './src/server/db/tables/*.ts', dialect: 'postgresql', dbCredentials: { - url: `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`, + url: `postgresql://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_HOST}:${env.DB_PORT}/${env.DB_NAME}`, }, }); diff --git a/src/env.ts b/src/env.ts index 0fde6312..f131724f 100644 --- a/src/env.ts +++ b/src/env.ts @@ -8,16 +8,16 @@ export const env = createEnv({ */ server: { NODE_ENV: z.enum(['development', 'test', 'production']), - DATABASE_HOST: z.string(), - DATABASE_PORT: z.string(), - DATABASE_USER: z.string(), - DATABASE_PASSWORD: z.string(), - DATABASE_NAME: z.string(), - STORAGE_HOST: z.string(), - STORAGE_PORT: z.string(), - STORAGE_USER: z.string(), - STORAGE_PASSWORD: z.string(), - STORAGE_NAME: z.string(), + DB_HOST: z.string(), + DB_PORT: z.string(), + DB_USER: z.string(), + DB_PASSWORD: z.string(), + DB_NAME: z.string(), + S3_HOST: z.string(), + S3_PORT: z.string(), + S3_USER: z.string(), + S3_PASSWORD: z.string(), + S3_BUCKETS: z.string(), FEIDE_CLIENT_ID: z.string(), FEIDE_CLIENT_SECRET: z.string(), FEIDE_AUTHORIZATION_ENDPOINT: z.string(), @@ -40,16 +40,16 @@ export const env = createEnv({ */ runtimeEnv: { NODE_ENV: process.env.NODE_ENV, - DATABASE_HOST: process.env.DATABASE_HOST, - DATABASE_PORT: process.env.DATABASE_PORT, - DATABASE_USER: process.env.DATABASE_USER, - DATABASE_PASSWORD: process.env.DATABASE_PASSWORD, - DATABASE_NAME: process.env.DATABASE_NAME, - STORAGE_HOST: process.env.STORAGE_HOST, - STORAGE_PORT: process.env.STORAGE_PORT, - STORAGE_USER: process.env.STORAGE_USER, - STORAGE_PASSWORD: process.env.STORAGE_PASSWORD, - STORAGE_NAME: process.env.STORAGE_NAME, + DB_HOST: process.env.DB_HOST, + DB_PORT: process.env.DB_PORT, + DB_USER: process.env.DB_USER, + DB_PASSWORD: process.env.DB_PASSWORD, + DB_NAME: process.env.DB_NAME, + S3_HOST: process.env.S3_HOST, + S3_PORT: process.env.S3_PORT, + S3_USER: process.env.S3_USER, + S3_PASSWORD: process.env.S3_PASSWORD, + S3_BUCKETS: process.env.S3_BUCKETS, FEIDE_CLIENT_ID: process.env.FEIDE_CLIENT_ID, FEIDE_CLIENT_SECRET: process.env.FEIDE_CLIENT_SECRET, FEIDE_AUTHORIZATION_ENDPOINT: process.env.FEIDE_AUTHORIZATION_ENDPOINT, diff --git a/src/server/db/index.ts b/src/server/db/index.ts index f7a227f9..dc1b47c9 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -15,7 +15,7 @@ const globalForDb = globalThis as unknown as { const connection = globalForDb.connection ?? postgres( - `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`, + `postgresql://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_HOST}:${env.DB_PORT}/${env.DB_NAME}`, ); if (env.NODE_ENV !== 'production') globalForDb.connection = connection; diff --git a/src/server/s3/index.ts b/src/server/s3/index.ts index 57ff7420..ecdc492d 100644 --- a/src/server/s3/index.ts +++ b/src/server/s3/index.ts @@ -5,7 +5,7 @@ import { S3Client, } from '@aws-sdk/client-s3'; -const endpoint = `http://${env.STORAGE_HOST}:${env.STORAGE_PORT}`; +const endpoint = `http://${env.S3_HOST}:${env.S3_PORT}`; // Cache the S3 client in development. This avoids creating a new client on every HMR update. const globalForS3 = globalThis as unknown as { @@ -16,8 +16,8 @@ const s3 = globalForS3.s3 ?? new S3Client({ credentials: { - accessKeyId: env.STORAGE_USER, - secretAccessKey: env.STORAGE_PASSWORD, + accessKeyId: env.S3_USER, + secretAccessKey: env.S3_PASSWORD, }, endpoint: endpoint, forcePathStyle: true, @@ -26,7 +26,7 @@ const s3 = if (env.NODE_ENV !== 'production') globalForS3.s3 = s3; -const buckets = env.STORAGE_NAME.split(','); +const buckets = env.S3_BUCKETS.split(','); const imageBucket = buckets[0]; export { s3, endpoint, imageBucket, PutObjectCommand, DeleteObjectCommand }; From 53d759fe506a18db9bd6aa8710cf0c0aebbadcc8 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:49:34 +0100 Subject: [PATCH 45/93] chore: update matrix code --- .env.example | 5 +++- package.json | 1 - src/env.ts | 6 ++-- src/server/auth/matrix.ts | 58 +++++++++++++++++++++------------------ 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index 6690cd5f..0c689ee4 100644 --- a/.env.example +++ b/.env.example @@ -36,4 +36,7 @@ FEIDE_CLIENT_SECRET=" " FEIDE_AUTHORIZATION_ENDPOINT="https://auth.dataporten.no/oauth/authorization" FEIDE_TOKEN_ENDPOINT="https://auth.dataporten.no/oauth/token" FEIDE_USERINFO_ENDPOINT="https://auth.dataporten.no/openid/userinfo" -REGISTRATION_SHARED_SECRET_MATRIX=" " + +# Matrix +MATRIX_ENDPOINT="https://matrix.hackerspace-ntnu.no/_synapse/admin" +MATRIX_SECRET=" " diff --git a/package.json b/package.json index 5ef47df6..57fa8829 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@tsparticles/react": "^3.0.0", "cmdk": "1.0.0", "country-flag-icons": "^1.5.13", - "crypto": "^1.0.1", "cva": "^1.0.0-beta.1", "date-fns": "^4.1.0", "drizzle-orm": "^0.33.0", diff --git a/src/env.ts b/src/env.ts index 161640dd..3ed3b673 100644 --- a/src/env.ts +++ b/src/env.ts @@ -23,7 +23,8 @@ export const env = createEnv({ FEIDE_AUTHORIZATION_ENDPOINT: z.string(), FEIDE_TOKEN_ENDPOINT: z.string(), FEIDE_USERINFO_ENDPOINT: z.string(), - MATRIX_SHARED_SECRET: z.string(), + MATRIX_SECRET: z.string(), + MATRIX_ENDPOINT: z.string(), }, /** @@ -56,8 +57,9 @@ export const env = createEnv({ FEIDE_AUTHORIZATION_ENDPOINT: process.env.FEIDE_AUTHORIZATION_ENDPOINT, FEIDE_TOKEN_ENDPOINT: process.env.FEIDE_TOKEN_ENDPOINT, FEIDE_USERINFO_ENDPOINT: process.env.FEIDE_USERINFO_ENDPOINT, + MATRIX_SECRET: process.env.MATRIX_SECRET, + MATRIX_ENDPOINT: process.env.MATRIX_ENDPOINT, NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL, - MATRIX_SHARED_SECRET: process.env.MATRIX_SHARED_SECRET, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/src/server/auth/matrix.ts b/src/server/auth/matrix.ts index 703bea5d..65bfbdd7 100644 --- a/src/server/auth/matrix.ts +++ b/src/server/auth/matrix.ts @@ -1,10 +1,11 @@ -import * as crypto from 'crypto'; import { env } from '@/env'; +import { hmac } from '@oslojs/crypto/hmac'; +import { SHA1 } from '@oslojs/crypto/sha1'; // Get unique nonce async function getNonce(): Promise { const getRequest: RequestInfo = new Request( - 'https://matrix.hackerspace-ntnu.no/_synapse/admin/v1/register', + `${env.MATRIX_ENDPOINT}/v1/register`, { method: 'GET' }, ); @@ -20,7 +21,6 @@ async function getNonce(): Promise { } } -// Generate MAC function generateMAC( nonce: string | undefined, username: string, @@ -28,27 +28,32 @@ function generateMAC( admin = false, ): string { const encoder = new TextEncoder(); + const key = encoder.encode(env.MATRIX_SECRET); + const nullByte = new Uint8Array([0]); - const key = encoder.encode(env.MATRIX_SHARED_SECRET); + const nonceBytes = encoder.encode(nonce); + const usernameBytes = encoder.encode(username); + const passwordBytes = encoder.encode(password); + const adminBytes = encoder.encode(admin ? 'admin' : 'notadmin'); - const hmac = crypto.createHmac('sha1', key); - hmac.update(encoder.encode(nonce)); - hmac.update(nullByte); - hmac.update(encoder.encode(username)); - hmac.update(nullByte); - hmac.update(encoder.encode(password)); - hmac.update(nullByte); - if (!admin) { - hmac.update(encoder.encode('notadmin')); - } else { - hmac.update(encoder.encode('admin')); - } + const message = new Uint8Array([ + ...nonceBytes, + ...nullByte, + ...usernameBytes, + ...nullByte, + ...passwordBytes, + ...nullByte, + ...adminBytes, + ]); + + const hash = hmac(SHA1, key, message); - return hmac.digest('hex'); + return Array.from(hash) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); } -// Register user on Matrix -async function registerUser( +async function registerMatrixUser( username: string, displayname: string, password: string, @@ -66,14 +71,11 @@ async function registerUser( mac: hmac, }; - const response = await fetch( - 'https://matrix.hackerspace-ntnu.no/_synapse/admin/v1/register', - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }, - ); + const response = await fetch(`${env.MATRIX_ENDPOINT}/v1/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); if (response.ok) { const jsonResponse = await response.json(); @@ -92,3 +94,5 @@ async function registerUser( ); return false; } + +export { registerMatrixUser }; From d1910f062e2ab2c54930e3781753c92069d50128 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:57:09 +0100 Subject: [PATCH 46/93] chore: update and fix tables --- bun.lockb | Bin 269724 -> 270468 bytes package.json | 1 + src/server/db/index.ts | 2 +- src/server/db/seed.ts | 140 +++++++++++++++----------------- src/server/db/tables/locales.ts | 21 +---- 5 files changed, 73 insertions(+), 91 deletions(-) diff --git a/bun.lockb b/bun.lockb index 30addd66e0b32bee4c7a154c64526a77374aecd1..a9ec711e2eaad1d52a8d7d2ffe9362645e11549f 100755 GIT binary patch delta 47310 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ℜ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 delta 46699 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 W?q!!G$}j5NfXeg8PM;;u@(KW7n=gp~ diff --git a/package.json b/package.json index 57fa8829..5de800fd 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "client-only": "^0.0.1", "cross-env": "^7.0.3", "drizzle-kit": "^0.24.2", + "drizzle-seed": "^0.3.0", "fluid-tailwind": "^1.0.3", "lefthook": "^1.8.1", "postcss": "^8.4.47", diff --git a/src/server/db/index.ts b/src/server/db/index.ts index dc1b47c9..dae04547 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -2,7 +2,7 @@ import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { env } from '@/env'; -import * as schema from './tables'; +import * as schema from '@/server/db/tables'; /** * Cache the database connection in development. This avoids creating a new connection on every HMR diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index e86217a0..a597eeec 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -2,98 +2,92 @@ import { type InsertSkill, type InsertUser, type InsertUserSkill, - locales, skills, users, usersSkills, } from '@/server/db/tables'; +import * as schema from '@/server/db/tables'; import { fakerEN, fakerNB_NO, fakerSV } from '@faker-js/faker'; - -import { routing } from '@/lib/locale'; +import { reset } from 'drizzle-seed'; import { hashPassword } from '@/server/auth/password'; import { db } from '@/server/db'; -const fakerMap = { +const faker = { en: fakerEN, no: fakerNB_NO, sv: fakerSV, }; -console.log('Deleting existing data...'); -await db.delete(users); -await db.delete(locales); -console.log('Existing data deleted.'); +async function main() { + console.log('Resetting database...'); + await reset(db, schema); + console.log('Database reset.'); -console.log('Inserting locales...'); -const insertedLocales = await db - .insert(locales) - .values(routing.locales.map((locale) => ({ locale }))) - .returning(); -console.log('Locales inserted:', insertedLocales); + console.log('Inserting user...'); + const user: InsertUser = { + name: 'Frank Sinatra', + username: 'fransin', + passwordHash: await hashPassword('Password1!'), + }; -console.log('Inserting user...'); -const user: InsertUser = { - name: 'Frank Sinatra', - username: 'fransin', - passwordHash: await hashPassword('Password1!'), -}; + const insertedUser = await db.insert(users).values(user).returning(); + console.log('User inserted'); -const insertedUser = await db.insert(users).values(user).returning(); -console.log('User inserted'); + console.log('Inserting skills...'); + const skillsdata: InsertSkill[] = [ + { + identifier: 'printing', + }, + { + identifier: 'unix', + }, + { + identifier: 'raspberry', + }, + { + identifier: 'laser', + }, + { + identifier: 'arduino', + }, + { + identifier: 'souldering', + }, + { + identifier: 'workshop', + }, + ]; + const insertedSkills = await db.insert(skills).values(skillsdata).returning(); + console.log('Skills inserted'); -console.log('Inserting skills...'); -const skillsdata: InsertSkill[] = [ - { - identifier: 'printing', - }, - { - identifier: 'unix', - }, - { - identifier: 'raspberry', - }, - { - identifier: 'laser', - }, - { - identifier: 'arduino', - }, - { - identifier: 'souldering', - }, - { - identifier: 'workshop', - }, -]; -const insertedSkills = await db.insert(skills).values(skillsdata).returning(); -console.log('Skills inserted'); + if (insertedUser.length === 0 || insertedSkills.length < 5) { + console.error('Error: Inserted user or skills data is incomplete.'); + process.exit(1); + } -if (insertedUser.length === 0 || insertedSkills.length < 5) { - console.error('Error: Inserted user or skills data is incomplete.'); - process.exit(1); + console.log('Inserting userskills...'); + const usersSkillsData: InsertUserSkill[] = [ + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[0]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[1]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[2]?.id ?? 0, + }, + { + userId: insertedUser[0]?.id ?? 0, + skillId: insertedSkills[4]?.id ?? 0, + }, + ]; + await db.insert(usersSkills).values(usersSkillsData); + console.log('Userskills inserted'); } -console.log('Inserting userskills...'); -const usersSkillsData: InsertUserSkill[] = [ - { - userId: insertedUser[0]?.id ?? 0, - skillId: insertedSkills[0]?.id ?? 0, - }, - { - userId: insertedUser[0]?.id ?? 0, - skillId: insertedSkills[1]?.id ?? 0, - }, - { - userId: insertedUser[0]?.id ?? 0, - skillId: insertedSkills[2]?.id ?? 0, - }, - { - userId: insertedUser[0]?.id ?? 0, - skillId: insertedSkills[4]?.id ?? 0, - }, -]; -await db.insert(usersSkills).values(usersSkillsData); -console.log('Userskills inserted'); - +await main(); process.exit(); diff --git a/src/server/db/tables/locales.ts b/src/server/db/tables/locales.ts index 315f6e9b..77dde0d4 100644 --- a/src/server/db/tables/locales.ts +++ b/src/server/db/tables/locales.ts @@ -1,20 +1,7 @@ -import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; -import { index, pgTable, serial, varchar } from 'drizzle-orm/pg-core'; +import { pgEnum } from 'drizzle-orm/pg-core'; -const locales = pgTable( - 'locales', - { - id: serial('id').primaryKey(), - locale: varchar('locale', { length: 2 }).notNull(), - }, - (table) => { - return { - localeIndex: index('locale_idx').on(table.locale), - }; - }, -); +import { routing } from '@/lib/locale'; -type SelectLocale = InferSelectModel; -type InsertLocale = InferInsertModel; +const locales = pgEnum('locale', routing.locales); -export { locales, type SelectLocale, type InsertLocale }; +export { locales }; From e6ad005315090a17f380f8e44f461d7e0a8cb01a Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:32:57 +0100 Subject: [PATCH 47/93] fix: locale access in api --- bun.lockb | Bin 270468 -> 271004 bytes compose.local.yml | 2 +- compose.yml | 2 +- package.json | 1 + src/app/[locale]/(default)/page.tsx | 2 +- src/app/api/data/[trpc]/route.ts | 3 +- src/components/providers/TRPCProvider.tsx | 16 +++++++-- src/lib/api/server.ts | 40 ++++++++++++++++++---- src/server/api/context.ts | 5 ++- src/server/api/routers/test.ts | 4 +-- 10 files changed, 59 insertions(+), 16 deletions(-) diff --git a/bun.lockb b/bun.lockb index a9ec711e2eaad1d52a8d7d2ffe9362645e11549f..b101116eb6900046d2b527bdbd4625bb82e55e8b 100755 GIT binary patch delta 47960 zcmeEv30PHC+xFQTj&d{;M?}PV77#=PWH=5vi#dxUBBGEW3QlMUIGdWo)|Td!W16#; zii%?nm}7%!YG!I_<&;^XQU87Ky*7hcZ}0oP-+%qre|0V%?)BX3S@XEpUI!)}a$a)O zd5%||(rfRZxpeB+_~;P{n^H?n>)iCljqeUcoKKnK<2iVoN26sgRduxRGi+{;N~(A4 z+%c6?L%`T9mbAp^$fyzU^0!(nFF`*BB=04$h-$Hvh5o#~>A$nE#Zn6X2N0+za70pE z>;S}9qlm@wGW0Froq=xy%K&3x1ZQemf5VYB5t<7;HXtHKFd0}AcoVs)4BUYXlm@OS zWU*8L#=+ke*cDg~xCE(G0fs_nMm>NffhB-$z{fJ&r$E~6KyIp9QY~q3f?&=wfy_WG zYJ?f+0J08n7Ghz7Wr4J-A^G2+*Mfc>=ngz8?bb_71Tx{CKxVXw#5zExV+EE-g(IuH z#o`HE4y+3t1!Tfu601x66|vL?|Ea`{5+_OQ3uK1;fi;08Bz}+Tp?(s`l5YT30?q-Z zGKZt!KpJU%BsK>kY+7B3C4u-!dx~rmZva_>Qxf+8QJA#Z)h(8az$74RtS6B1HUToe zS`teD*+jh^Eunhn=7U3?8vbo}*(TM_7^)h7OHRv{+igpPkYZ$WAmcazwP} zprJ{FwN4H$so|)s;$W-SvskJD9f9mIH&L0y4<)VvvfNW7#sHb!wh}#oY)EH`KOtTg z;}Ve7lvUqIeGic7Z2}^WoC zIoa6A&Q^&6$mU4}vUvwf>)cvJ>y0jk%WIeP1Rswnh znGshYGoBP3KPoyg6%!B$)<9H3TtXsp-ldh%KNoSRN&2Ln;y< z2O9eKK-#H_5$;jNEJq*-KI4Tn5vsj|ylEx5M zfiKB6h>9OLgoHV5K0lql)WxWZqFs$y&YbU`pN0!gpLw&VIlY?GZNX_7)2unYn)9={ zV3_mtTqCDxlYuPR2q1e?jMN>0Y}}+?MyF{4oi*dp+sN=FAP1K#_*%dRa=qBy2R!;f z+A260L3&@Kcg&{;bkWb~ZR3F)2D2lKgvU!94rCQa1DUbjKsG^CLc);PXp7}Fgkvv` zPmGU^hn|TRK$WL$0&=nv15p1g$wCm!Kwy*+APqXl+jr0zaif6-KXOED9GmJ9bcP#> zNSV|d5<|Y*oYt+5z!zr z#;CdrKxX7sAai~g$P&E@JC4UjuNhT+3(0c0oCC5T9|D>2*r74eiLoP+;!&Vd(0fS? zN6j$4-Vv!rmCZ)ESc2B@VATWz8NtY*17o9NEtW6QW(-gkrJ?_E=p4vd&?^CV0GXjX z2}VZF1DWv*iDn;L4V@W$6G;1Ysl$y3t%*hgKS5{JzKsOf=97Rl9F}AZvmQY5AwYIA zParcC3p-A*on^*CfJ~?|kTa1w%BZs7b~nz_S3iq#l-L)YK$t{{%V<@IJzo;`r|j2Z?9tMvmep z7y*WWX9>JsH%gj_2$=AWi55$BU~lMbf)+q#ybRRl7l zpCDd0ZnIW`V8mHdjf4tGgFWMn5+8?N8U7=u7`O^N6FxKDNYD`pRE0huVRRDr1mD8m z9r`^az=TGQj74{}Shhp20(~`*HTwpzJZdQ|Q5r@9S#_;}%=yk)M#PJOY+JJ?oZ(*) zdh^-FYJD0=`+zw{cu!=I37AbY6+D~tMNN~+l6;E*b%4i!%;9Vxhgl+!B{3sBGuB8r z2t31$OE%j6@_b_+NE(qibY#56avVCtcZMBnt|*W-v;^VVX+L|*sF@$eqW;-7JK>>M z6_8V8Z)w<2>SchmyN?8G0`D#|n(P$xYS7n9oCah8$}BMo6ayWdEUhDu@dp9fr>X$U z1AkkJap(rewWUS^j~5#`4qa{ps0U;O=fShnZIk|UfJ|tZ)NL|>sfsb5i~_PL!-1Sv zl14@)MkYmDu4r?Mxuou0ZLlL^qKC!~O17+$`eq;}^p922EwM?_V=yCHlHN7iq2)Rwo?($AVmyaLCy$r*FRj<7SL_R8hsF+zOiCIF z4;vyp43FYKm-mdG6DRqt(Dlgx$c!!7VAv-Bi$HI+(ZFEnoE$DfXU8}VWJWC#3x>m( zdp_Wf%|^n{2Rw|LU>Tkd`xIi9%mym6&~6-$Oz-tIXV6wuBw$(;(UR z5kX=^1hPa3=~_1A!msJrBw1DOFYARF6LF)|(>Im|O* zz-yL4M~zCV0-nXLdd#S#ve4NEC!n*l*pD0jGl0%q0cyiRkLaWUBV*$RTAWWB`qL~U zr#B^@k+@spDj+j58_32UCvmXEE)wk$>q&Hx=s;v|d~m`j*;R=rCGL{A2FRMwB&JJD zkoZ1I#tf_mvZ?-^f~2x7{wpOxJpVyKit8maOA>fq`>tford2K(J+>^6O_y`gXtJ_j z7%hS^8yOclFxm#46VqeZu>!hXHo|SyI+bz>+|tqL09&pYvHS#N$5;!r0iWm3pto~j z?X9gS<g?2cXcT${gbbQM; zW&G;8JIcAeV{6;1K*w~mGOnif(o$m)ou>Zhe72Gh|}r`EyM zoi5vNaG|}@R2I_O0h4M6EvHtHH50D>v{2oPYh&w#S_fmLYNiKX4_8;+l>=9I-MkC> ze}CQe8C(POfc_=4#`Qw2wM_3I>rJ@2=q`WsqNciQ3|xlq zEx2NJt^-DUH{G=gE+fRxxvn;7{Z3j)K(HemVY=D3xvqxjB5iao1+Ea?^>cZPWu)#J zf)19TyS{+SNVZi4)AfF?s|vPbLv*{DxvpOfS75Ls`mW)58ZP7*IjLl^4A$+J=DHj! zo7@n%`s%(X;OeQns#IYiSSg8cy{fwo>8=o;V8^Ov!TRR9-h->1ZuU6W)uftP*y*{h zkKwZGcAKhOEIo9WV-2$wd%*Ro7Sbq~lu>t&a$QYoS}d&?K$)s#2idJ3L2s;OqDz#l zrHu^^Rk~^!!FFYamK|)jU8`lWG|@ZXA#ZxtYiKs@P(6QJ60`tlj@qGw04+Pj zu3XmwL+v)#Iu?t!5vHEMGFZz9wOd=&vsj|F%+Mg~Mz|t$S5Xhs^;)hg3ocY5>}qnhQRxhEHw>pBisPo1mmZ@QA<>Y;Pz;OeHkYM^=z*I2l^kh7hJi?yq2 zN^O5z{Xj!=(3E}wT3~CtZ5tS5JgvBvz97IU2$AYa(}MwyRJ4@VC>dPcbXVJ8#!GH# zuIm9@opi2g2>TIz=fKsDF6FY8(bjJL#m>>6**3`99NnXX?wSi%OWpN3T%lS{NRYKm zQ%)+dQ)0Ei_IB$j=;69e^=2G6DSd(+;V`259IoD4Nc$jbgXWmNbh8O?Md;l1T+Zhe zlbZvV9@cgRuHJg5OAq$9`nO<1FsAu%Me44{a2XkG-V)P1W4En@3$2${SX0ndFKB@s z?P}>(+SrbvN`#it(XQ;$vOC&sh0y6a1zWUqm|?BxWVfY(Lw?Z?O+EaT!&-JHyJ~Br zHSQd$w9t|}+m$peqqE(1r;Qo3>EHlIEFXS)E%wopyV$MoL2t%-vi$%TsvVPj`ZBa* zSG(=Cc1G5~)b+Q%2dxRGU*$V3yQ|%r*`6IYr)!W}F-#lVE!4Iy%vhYzEkga3AGPdm zc4fF0*xhc+>R>Et`chzXdlhrLUKAzR-!>eYOm@7#vPlcM$Qo)Ew>9A);EtJYn5(X-{7*P!qs1o1s1kz&<2<(Sev3S zuV^8CgB{^O?7ejd)-kkweRO>+d%c1k;lQegNUy?$RjX&PV>nW!m6D`o^tIa#L1%NK zObY@WdvbP786TuH(~|qyt&^a)*HZcgDc@38>S%!xcI(_;m<_d%h#>V+FKtgmsMU@R z*h0&U2(oE#v0g3Op}zjb&O(caX09q7fV%DUm_SJ6hlXJ9qC#zFBh7Axayy}3(XrAT*r%3V^FKmsrT4`!Eil?{TN-6_2L~#5IULO;BDY)rt{eH*xrN2e4_)m_P2gXKklJyl?NLeIX$$k zSFU!bhrew;G)$BzYtv|d<#R1N#;%r$(Hh5w+D62nR~V6Z^0(cChNkDhYwd4q8EaI4 z-j}WOprN8W1zEp_3wSivNQ=u|?5(b^Mhj zT1LEGIj?2M+pRT+a+j48A7mR07uye+>+Em64XqE`$@WTuImDXI3~+?fn6%Yr7{>=) zskyGxaJAPt*Wr5iM({|uS|M6}$x(J|8N=*0mqhesJv&OQztUX`9B#L2;5zCF-hhi8 z22EDS-#Rl%ADT$#Yq$)H1|y7FNzo341UNzo)e}7oS66-IC_9oFVLc3hi}iqdM9sYi zjSZ$pZ7Vy<=uSpLW1#8#U+WpT5D#{|RY$Xsh{rk%uC_Y22QGa!wLXO_SnqQk$K;y| zZ8M>A+bp=)Ay7S?{J9w#Yq!QsX1B;08>D=wC67aLQ?xzf zLTwAC7>S@HQU2Cz&^qc-)Sqh1J>Ze+$LbV1=;4q<&AJQ&OEjc zp+%XoSZmE>+c5`Ga2a{o1sCdlRFJjsEVc!G+rWjI9u;J}2-iR(JYoxe(8t_4oETU%@Twg_cf1y_g` zGC9cl6*U26qpx1l_mqLOle0x5H&{RTh~c(%_0`e74`=;@qXz zht>WzcYch0ZLX`>VzjKDcIXluz3RT(;KDkCoIQaHX(OgKON}vP)a-0%tV>-}U6*NN zXNKB_EkidlN$W{yC@3Q7zT7OpyKos1--9bwPpro~rrmD1kQHRlZH25P+v{*~92v=e z3N6^sP?gRrIjy7w2Pp%!D*A;qP0dV zqaPx`FVGm=QJ*>e-ZiwsTy1SjpfxZufrU6%;U3PiE_YJJ4ATXgKB?Ijz{N=s4;$E2 zoYn&8V^LkNjh!EA>$u+VD$EJrHVqm_gN<7{+j(d$^^`b|t0Gw7sO%rj_gPgD#R82`zyif^}A2I(B<`;pMFV8sS0+qA$XcH7U;yTiATUh%zln3a3z-2g`@4fU$HBI!cR zwp#gpZS2xeTaWjRsRz>^c0OyMF~3;WkdJH7yrE&;XcXY|fl(poMQFcpXly9`xySZ4 zv=-2c>s6N{E%jB?7P!-lgjF63jbjxR66$ZwfQDTTR=;21dPUD=(5~EcV*PDt(3l~l zgU-GOT0>|U7U;WAq$M8P;MZ)oQD}X4YMTv>;q=a8%aWSDcTiu>&>F7{wb?Vw(a6YB zp|SP#-HmN8G!7ZmCu$`dnyeM{j4pf33H8tvf7?`Ow6wCul%rbqD!b~kS8KdF)Y^70 z65zvyEe$T#59a=8f9rW@*c;+`ukt>#q0^CtLD1~F701gOXkO6tU7YO`Xu-P1U10G{ za|m*>=q5Fk4jY461^kXeV^`HnZY#gv=w`+mG!U9Gkg-p+ErG`LP!JUPB($cw#&#-t zfLjT6w>T|%o!zSbqQQrz?*zDzMR4cf>P(ld z>QQqXu!V;~Ym{5Swa{3h=q~mA)vu3gdwPW0>K!v08iO8W^f7JhrcmXYma)mMwmYsp z-4tqDeB78>jlO*YS{uEj>9F@dq3zimYOQsWOL6ArAnPc&aP9`zkzAMaM<&-d*YzG; zIPQbpW4Lglxg*%|l}6*X4TJ^c|4v+K}t|Dc2QphD#hHo|NnQDA!f;tm)em zEo4|as3x&DZ;6)tWg^zE;TCy=M_hg#d6XTfvc53;@uSD5bl zHrLhgW0OnIb)A8$oo?s)2}_id5$tF<5XpvI*H5{wkWY0p_)f`noy~QX`^@x>gsYVv z@w!~s{ajb03#M-hTqqjiJDlq({JF`s*IgmVlu6kxz|{Z+$8?EV(CMPNgmbza4$YWz z(A_ZEXY94xye}CeTz_7(je^F(fH}0Qzw*A8yw7g+_(Jd2c)*$tmmH6nN)Bm(nRc7w zWn%+@b+WU+I`Fc#Co|N#7vw8iW+t{&aIsIL9pLSEMH{<6R2_0f+p|AZ*`sCex2u+` zTH^zuYSXLQSis1u+MWZU%4RM5fZcZgDpE#Zjm)Gv*3fz=ET?WeTt zgE$+$rXNop46;`LN+<$B$8#lVFQ0PX|0(8LFWRLj@e)5DEZ zn_KiJVEi`F0f@!ULd00ID3G=i+X49@lE9t?4W43T_3 zOw|(_3J)tJ3Brg+K=>gt;1~$`WC%0%I)n+1mwLL?CjuGIG)N)Hn-JQ~k@{OehF=6> z#@=C3Xs{AugKU7%U^9dtBK@~Qia{`a>pCV}Jwpe9{2YX+TF&Qo8?%Yo#Zq27{ow&! z`xRPYt=Ogdsr1t+iPVeW7qKXRNo0z}q)udxoTdIEq4=ss1;^JQX!s3~hIfEPfR7~q z3lKk+C-_DGXFw;Q19HKU%`3Fzi%E0_G6Q9$zdMleSCRhJf&5fYg@aX92gn5K0_o?$ zUlQr`!Y}&QmpqXPHUKiA0Lc@XP%x18ytT-Twvhfr>S0p%GaY{i8L%4zYJI=togX}P zKw~mJWOPL8;XqQoBu`{9BP0$4(k@#16R8i9{zIf5C;eZ5sd|8TX+UHGi9puJ7$74| zmN*v3`bv@dcpyJS+OZvoX+U~S;4g{fCrW;@#Hp$=Cs?LR!@oi%{DurT6X*2Zj^Br>Omq0^8TYF&X}0crBJ#G61iX=;>W^k{Y>zo#798-KLavDzXF+21*s4V0m&DZ z`b$h5A0qi;j_66;43w1~M8sinl~@7D1S?9t3J^b*8WQUO>0b}X2t9yI*aygX8cOsB z;>Qvs^-v(gHA#ho0h>yXSEL8OibI3e(!Y(=+W{GI2dQ@ia?Ev?dN`0Z83DwPB?`aT zlZFDBP6A1Zthv%9R#uqp8_JiRLjS3)CTcXVpY^16RH7Z01qHD;wh2egk#Pd0h!@oU`^lvsmB00 zRz?E3jHCkbW0{0sw10z&M16NL0|W=z3Lq0)Ck^r=GqeFbGqO?QCTaH~q}O)*ViUeE z?TCzL7m)GnCeM=4p=hVi8G~^zX?*e{&G*S*nbM!g=nqJp$nqTpGMN)VhRc%vMDnMk zo*!v<+KR@Y!8sZ5yfh?||5WOKg|z!z+7X$-OH$8|w7VktJj7sRPOi#;*Q7yyWQ5nj zGlH8ybQ;TTX_p^q_nqX4w7Umn{P!jQ2TaZ9!3Z8ooydScN_-^oC+Ytpr2S**Po(}c zkkm8D6UqMyWO}K;N#b{DK%|F)hNYp(2L2bw9#urz5gD$i#Fr#bWV|m+U1F+EFhFs5 za7@&Y21HhQZK)G!S4ZkZ4mMw@|7VEye^EfToxhAYKe7aYk|)wG1W4WvWCoi^e ze|3gq`R52mucCjBVE#FR`R52m9}4-7V*WXTG1iiQj$rhC?LS8_{~W>ma|DzBD26-S ze~w^`NizS@3l|dV+?eMby^y7jM+QCw{c{BK&k;=iqnLk=U^qPTpAG$U1j7mBMMp2J zDf0hM9KjfeHuHA4emA0-2=P?fiHjaeF|hzH<%qcMsq_%jJVCVZ0&!H#^#T##1>!M@ z2oku6-CfGF1lL`oA7&qNl96C^yF zg2)lcO+k!l3gRjV^>>S?+f3;$YBvKjs~Jobaj6+hE|Lgo4#Gi9Zw_Kwa}ak)SViC~ zAOcasDLLBzBLahimy zaBT;o9Mel_2f|HckvKuZvpooRk=!1{nD!v9lBgu=gn_6X24Yqih$`X|iHjscI)JDq zrgs1_tpkX=Bx;DjS3v~43S#N2AZm#_ByN)k>jw{cM0{5eej7q?Bs_b72ocFWK#b`D;wlNds1pvNb~uPx z;UJodOC&Cm2M+VH4;R*NDwKJAbN`|5+_J_4gk?t zBo6>FW&nt*BqBteC=j)yK+K8)5h*T_xJV*oAc!b2eISTw13}y+5iJ6vK?FpDSQ-st zu((6wHi@u7AY#R$K_C_m0+B;vh-fnyMC-vIHVp<5FS1EIA<;JmM1oi!17ckah+?rI zhKruDAi`roWRge{w%0&7y#^xwH4r0328rDy+=qY|EnK&48V90W9Eg-S z5aUD^i4!C|<3Xf|D==E|LgI05MTaPXIA30mNMr zlSSY#5COwLEFA`7s<=bqHi@v|Af}5&!$B+@4kCxd4ACYLMC(Kln-W3H64@l4km#EP zVzyYH1Y%thh+-o^%oROHfCwJ}B9nwBY$HK9jRX-t62t{u|@=r0}(I|#L{sf-W7L9+$ItBI*9dR(d!@f;0emsbE<3SWl1+h)^Oa&313L=xl4q;0J;gkjPLs$Gt`k6%n*btZ0*JjLi^K^Mo)bZ2isXqP#!LipmBay2XA+3o zlR(Ux1mcjmMB*ZekjWs9i0P9-Oq&eiE{UTea0-ZkDIk_k0dZX1A#s~T*i;Z%V$oC( z3#Wp}A@PxDGYv%RX&^RD194hplXya+?{pAn#ro+W)=dXd>}B6cQ-n3*6>lei>YXMrd;3q;B+5SK+3i4!C|-vn`0 zB)&l_C2>mx&IJ)L7sS%J zAifoMNZck7HV?!dv1lHMh4Vna$ zA&6%p3xx8ksImx@)-N)NtX1$F7in#PPOfHfLSq#EKOkWIQ z+F}rQNmxbT5)c7PKrCGXqOiC_;x>t}r66o#(Nd&VL_8!E6>XLQUJ@$^#Y8sYWzlIl zptx91a2CotfD)o7p`_SGC?#ww0HsAFp^V4?h}|m?gZoNk-9;4>JdpL6m0)gpAX!%t zyAmelR)IJT6KpnISAjS|B4rf_caa4`sVJ(f22>Ks08x7lysxf?cNI}*4fv`eols3& zB2*VXYXLRHbV5yWolr{zz6+==<`U|NJA}HT={i6?v54Ry9uho7oAm%Mv4T)vWD~qa zr}qFpVm+aOP&NR3MNdLQv5nv-Y#UkLjmTX5Mr6)kWRQs21j2n2h(Hm$35JcuAwrOF z-3$m8!wDfGix4WRYysFsGNFk$PiQLYYy~tE>4fIu65$o$vklNfOeeGy*9ome;C4W3 zF_+Lr+#$3TO?Lp=iA99=;vpeSw0R%UL98IWDzX7$-3Q2O-w%-0PGbEBAi{ToD7F(s z7twPk2&Y{jGD&n3wp}20lZf91qKC*J5wjbF`)&|DMeJ@6C!+Xo_KABbo%eIJN`Ob~ZT z3>JZzC{m1=ONbSB2(O8z`vF75B0`*aNQf704giLV6@&zleL&fz`0v_wPdfEmmt5J zk1IEfWuhwH`AYd!bzUo$T~}J#CcLX(pGlj!>+W^sUDf&)zJ^#-D5sUddTQpM^;@>9 z*ezw1Vs*%XH{-dXFdj#-Pz%vf>ZQRJD&5Fm3K3IB}df-zw`?b4JFAdCDo*%pX7M4gLh;3X#@^G z`ptwW$?-}zIo@p=D7nTUm@scSE|eU;W21At*{J6nfNS8!OFEV|i?QItJK+q>E0(-L z&+wt(7_c~GzvS9VJJ!l8;8^l@k}CoCBw3L5lH+%Cc=w4PUMFW7rC9#StRH+jfW!~~ zuAF5GIF`JlG%N%6G|6?694|@L2FH6Eoh8TXMs+3EMRMi9RRhNw>MA*2edaw-z5aP= zow+FwSw|8d^XfXkPf$lzp?Q710=Q-f!w;{rGpsx0JRGdGo|3Bw_ve!16?fWIf?SeZ zZ*clIEGmP%BFR1=@x#A%YPl-8ell=XaEHOM86qTC4eom2_~|dX>Ts8qT%_b`faAp? zHq8LZ)nxg3?~H8|1(H=-3&Oj;yzOn?kFO1PGs*F;Jmand;ay-h%{a-`l^mPlb;;F( zI~aDXa`S#YN6ij!j>!t=>iFd^n{dwAM29e2H>Aj=Nxef!m8u! z#@URM7bdHU`j=CxK;hSK_+_US5dLk_R*=@B$}qKBoz8Ib>pxQ=(;z_*euu0Cgx@XW zclY?6w5=k3m|CyuKB$~VCPT(SUWcSW-bZ770NH63$A+mLQsco5g(N_RL3j$zsU{S{ zo3DJC^abQHgmXmWSu@49MActuB<=ugmB8>zfhR?sBsC)S1`^|KMc!}Zop4?h z=dDiOW#yZ?Pa&T{ESc){%r z;g=HH5_`RXwfx!Mom-S<6^9S~l-=f!IaO2hln zNpK!OfP)bJJ$bGrTw}%~qSqk{AT=QeV9Uwv5QG!j5eO%-eGvTuH{xrHxVXk}O-bQm zQ48`h3_gK;1mRtHT#VPhOT`6+>qlP**NiksIwS%T3E{lYd2J|U7-Tqv-?iej%!@lQ zkVMF6NKGW}2H_TfOFox)FGmnhkk~B>%7db9!T*M|pxF9uwaJ}LB!Y`e03CNFJJGfT(A?}ulw<5#};Yt2o$UVpn$O%Xw zq$-3rXL*mhc_EAFF-mRPt1FalknWHk5HkUOiG@O)LO0iplaLdTqY$naTpp;Kywe!u z0H2QY1eS-Yse_^N+i9##);?Q;OFsMLX4rAh;tFwrl!n+KEQAxJFvJ0(LY5)(OCjuw z?3axoevpO`UkLX^-oW}0FNi0kwu0}Cvs-YZ;|6qvaHQfZ+?KB)*C1CQ_&&G=Un93% zf*gf>2ssStx$Dqq^@vq0O;LM`cT&_6?nl6KPy7+^7zDM#Wx$dJIW9^htEGC+Q+ zdQ&&uC*kI{fE@PkmU9q%H{Nm_ash%b+gm0o%gNant<%L0>50)2%g|N|^F?^~g>i)cS{=|=oPN{1BRGNGWF}-TTZPpI!oAt~( zHaWV#l=@ddG>zptWD1*DTf$bAp9fSr63Pra0hZ5 z!hrXI_aJv6KR~{Rd%Z+ z`WeWgz(Np52&c?nA)G!rbvl4Er%rO_RB8p!_1*NRZDHtKV)Ap0&q>St`#7ap>y%8DAqHQiXtmrt>wxPW@~T;uOX$p-haw?K&^(r+>bNT>X5p^2kBLH z2jvcxX>gn}9qzW7Rvq|NP{ihmYI%`1K`m|7Gb4&6t1czXDD$E_G(okaPr z#;6y{rPv?Sbv4uFa||sTAcUU2h#aH3iD45}$J}5{wywA|Q5|AsmMV!hlT_bS&Z%4; zDN7*pAafyHI=O_7gUo`=gm42f9m2icRLB$vzYd-b;e4MAnFvXPjDc_|=At$lG78cQ z(gQLQG6M3NVo@zPft;+mq((yT0*QchhjfO7L0*Bhf^>oeL%KqmL&72bAblVmA?+dU zAZ;K`A)yfNKVL?zga8?qZi?x)ORfpOhG%-V2FXNe#Dr+n48jOa8*--3$eBUzhs}tI zEg`18$>+CY-1Kj&hr`IDLPOJ-r8nKL!rdXCZt^CwW_v;yhPGyHF;3PvYnC<23@{99 z$V^YSNd>`bX9Q+KtZG&%4Y~o%#GXfnGb3iRFpSv*3`;RH#;}anHM5*N~8T_5MVIGG&b9ak(m*aV)4LiU0D zHA9|u95u|y7`8u+%yy#jcu4+gejPfEQ-ByUxe>CIYzFptvue!*%;rmnA2~DL=QEi) z0iI?COd~UKb`U1;e5YcBX6a2Ey)@v>1k6P9>o0Jp=H|}yWE;K#F{_a!W@2VFGDBu* z^SAGd5@g}>hh=7&(Z#^GA&a>3E`(zNr-~VRjP=l!j#^_umI3HrR zs|L3*PK|bB+X-+i&07#={Q0JO5jO{R`D-GtiO~N4vW*bSO%+8S*=X5i&pygvM;gUm?#ROz1ZVIl7la*dN#r*!HyNqql5Vy`Qju zuurhRFawV8XFp({VgEo})%4E6PQebw4#H|-jnKFdWVs^N&Q@zSD*|^}NErxgiy0^f zH*3is$hSB+FtwD2xI^3^6(Bs=C^gBv|RJZ1OxQ;)xMYoq9-rhA|I^cre7nARY?!hwxA+3K9)T5bkr;N=4G!(;_%xA?|#zE|E1%k^z-)e_43BygBUqa4RZ@Z!g$k^R=HGPuW28&IHenSd--6! z7gy)0u5RJdpzV{aIP3jYU18ws)zHhQp(RR`&{S7tl&A-An+m%kh-i6@G>>|dqX)pw z&#RG_FP=9<1PuIE!2okY+Bf3mpby8m{-7%Te7y9OIG&v0^Iaj2%-6oW`L6B*KMWGw zjzHY39X$7>ZMl0p=-$v?o1{SlbUJYz25J#i{Hm$t+$y8!cu-3VDSKmPxiYn#Vc5XS z#|wrbqW*kjf=4=ddQ02(=<>uZ-`pOW7gH>J@SK(A=@i&=Z!_hJ?$dyom?TmV)a`8; z;6W~}(1`H4VIH4-C4<6Xhu8)K<+%8Wc3;A-Bmm2(bUZA;R z@dDKsm!QrrQ0w8!(X$0=73HvSe+wlu|4XgkJ11(SfA(@`RXL5MY>4lM81fcE-51MY zSIQAj+3ZtMJC|v8C0JFJC*q^G)WJ3$i8Fam5hM_w`A^8ll?!?rJGJW|RWS`(iUgth zx^;#BF;W z#@PAB+9C4?Z0WAoV*_9G5PxxKq3SBuE>z3oG6mgkJUgWE?R9Syzi^?@Fjd(JhCS6V zP~H@sZXy>e#PxRonWFf&fKNs7F946lwy)7MOB5ETzE;cezQ--Fp<*sQyNVK@0&q3p zB4CF|zX6yms?cPEn0pYgQ$)V4Mg(RR*584>ph%|AABz;>gi#}kT*w~ zo)<6N5>j2Bi{hqOu~cn=i)FJ{sJ=F{=tjkh;AN`E^Ik@siv7#fmeox2KXwb#N4DS+ z(R8^Q;byWgurHXtY(FE|-$+I`^c7oIs3o5(gV`1Ia{3DI!>Y&M&ql#+C^MzU#J=Ub zQjPcg=)|?fc}}br1s=d-p7i$RMR}Qf3YtsVrLLuB?!8;;a-L7ga-!c_ ztYLNG&6@1_?!vPlUHiIMo<(yp7m+J{#0G%dDA;jH_%>&8{dTu5aZ!{tK1W=Gf!_ug zRD!`bH)?)Teq%_!Oq_&I75IFxvs9?#gddmY1^r%lzKgg^AOkgF5YTb_!}JNSW9cyC z_7{;bz-6j&w3`IG+OV5;WZA|p*6Ew`>^6!mFlh9tGzdO(-ls;HYIpJsew9A&4jR1R74m`SBLrEJK{jCZ%ZfSS&R}s>yU}%q8*?@7VL2SAT8v_h+8jT zd1GImo$LbtY2*IC63(wgb^W|Kt=q9r?tAYlF*2`&wcvw_OdI(|w@+s-8TcU2r-=yJ zfEMZr1NM^k8@~T#V&!E;^9+*2a2ViX_6)$kT~uo=(QKpoO0@&E{%pxLeFls58?n)B zgQhHwjJ(qSZmro*C(Kk8A1{ASB$jYdViPj5TGRu$?S~!biSS3S|FrhxJ8K+a7pU7^ z5)m-)`w0e|AQmRvI`_-uZb$MAoa-8EllS(dndiT~u`SQXQ*2_~ZDGLu$D+n1kGb@j z@p_&?jJVD`OcBp$w+40`Ikj(>Dt@~~xgYcFJ{0vgqg&sE0XG^Qns{EWoI32yJOjsi zhR=dZ4Tt>Z-`6S6r+PgxelsRaA?|KP8k_3lZ49EA`f}!^X9pI1sVb|$6alkGRN02S zWQibv_-(V=0hgh}wqSqNK%{ShPkXT#;5N8{QBxNae*M6I%ZTPo%Bax^;w%jOmcf8) z^`@&=9_+6boRnvtu7?9X-eRxq-aD3&wsdD6hG=qqdLt`7Y&FNW># zsfM6lgKythUHNj;JfDB7(tL4~IboYxuSl^#V*^(Uf;igVl4#<>4lbh{en z_qFuiyzbG|&TntaLYKtS#;1kv;lrj-*FHEoC}wv(Rq?}P1a?c7$70rY)zwRBtlz0i z`>6X{r$7F5LWHV#V_Eb;{>w`r-+57E4=u$DS*2KG@#%K8vRh5~;CftIy3c1LO4rlE zQ97AiV^MquDyAb0xN3Ypq3kx>CmxP@27}>)IXi96gLS7`jyhE>&nH#%MNl^Z1I`0m zQd4i2wB28rXRuYwg#orx8+M=v-4xfLwf+g_C1Kust5bM%?4-ET-0kU4T!n&+PStSd znzz2H+h=#RM$|k6}+E!sPUk4_=(4p zEhp}-bVMcjVvWM-I(*oPn(use_58l&de8LsYKU|Oik$aRo^%*+kRM<9+qvG~+**zD zpk)x!vQ+qdfb#4Wm3G1IlC=A5d(52ui|>RQc8ut;NQIrBi{0prRUC`IbFJ&Puk#H2 z;lt**H*MM#r&hx!3RE>NW$$H;FK=ZGjq~B}oon;c@>^I?*ff~P z*Ngi!cm@ML{rx=jtqG-ztgQwE9y#ElZMmqnTa9qq*Tz_{#c$pYE{+&fn((RXP}UJq zEENZKqr#T8Ge*~_J7?y+scpNhr=fSx8tsjKF{SJI)d$Z`II2hP$DY$v6w6RsSlhuM zRDbrVJ{?YYr2PzJJNCP<%ezk3M|>dXQzVjmDNM}CQ0rQCOX0IbEhp@IR4X>)5qnUQ zDPefUjDR@{cYJ(g#Tz-Q(f|`|06N#tqQo9GOxodTE_n~emJv!E+oP6OYqS?%?@?b- z@4qV2mf}^d9&IPlW-shQM9N-F>?ilC=hU?w#gct$xeDe#Umo|v;M3DeeqS|j*zN8p z&h1khR5;SnST9D#ZvLcIivP&zWA^s=^&RIK(ID~bxbuu1oM|JhE);KfyO7}S$5T9J`2&dkC2@$LKM>X^481kM%NQ4;}}UDy$!WS@h>OQ_-LM zOBKe65DIQ&8fI9N(5-QHP)am8q!x0^EM$~m*~@X~r?uTXkGm-&vaTW$H6Sa(tptt_ zxSN{vjdSG%g-hTlhISYgmV8As&MHb(F(UXBQS;$o(%IA}4N11lL$1dMpmw!4ntA=ixIUp8yL zuz#8-*>Bjo&u8tgbHf;0{{6hY5?BA&17i91SlsPc^9ame1{Ofx9lj9~Byl!Nt!JCs%UJk4#PXAvVk(?e%lk~~Z9LS} zSbNQ-$lD`%a?O1p2FuXsF(WW!Ea_Q4U9xxXx71k)?t`Ng<5bR!>V;)$#N+w-q7xFq zG3*e4+inEqv(r~02fqt_tLyuEGMt<(MrLsIoN*FayZ}3H_|wH*hMpt}f27uPTiDmw z?ESHB7mBctV0Tgs{z$Fj7u?U7OCQY{TJ7n>8GM$+jKHaY6*W$&zJ9$UjH#E1?o_u6rO^0!=aIeX#-t(b)v+{VM81Pq>*{K2i{&XP?yJ2lX^ zAG5_FCSj!Xhu0C<;c^R)n0krfA7ep_k2GfLt(%goU)}xl6yyP$6dI(#hx@q)4Q}op z>E9fW1*XqJ(f4$268KKWT-i=LL-xIcKmt87kVHMG{9~$NgC0C388AzZJBtpNxj-DRiWTSo5J#}61kx=s#;D>4 zyQa-q73@P`+*#5Q^2m^5umYBWLYJ3+wqsUy^AVvEyrmGUdJJ z^=iG3B>$|s@V06ws)1vuOVrC3>MiNFOGb~NVDuQpXnnWu(8BDZKSY-; z*H*3fIaJly}J>6R@tG(I7*$JcCn)W2RM<#0Ml39siNs8D1`)lci?Ki zUXZT`c8K5cLcx54AYV2Ve32lJv9m-3-!8}(5&EkAcTRxygX4%0^XT`F(}n5tW`fc4 zTqlP&cenOqpTs=M0awz5^fvEr^v1v4ZC{va5B)8ps*V!#k?+Guy+>dfq$dI{v^+O50nGOsFmmD zp?|kh3idl0Wq~4^FL3{7{qBWP%0a5nmVc*5%Ra1krh+r?-KKm}W}u~Hx2zHsi^+RMJ?DXD(0VyTiW+TTG33=#>h4z5Z^k$ML! zYz*v6VdV_ES@A=MN!QM*3f@2mFvK|F;|{xN;(kd7SGV=B<1ui+0rz{gqXb^)z|Nm9 z0Zs|!JEVVKxB=YCj5SUXLp)-tedC{wS=6*^C|;#qZ{b-S(T)@$r2tFBQij@zP~2a1 z_|W!!k8j<3_*#1$kWRX}HPfH5bE z`0o+(>~Y54uSxXLv%MoPxa7sWO00kZUKSQBjq)9X9iOm&{;=cxQGNFI!-9f6KXw9^ zZ^eB^{t(_i&XOK<1vSudgh{mucc6mNc&kWW5q7C zVv5-MSS{lSXCu+{E<$$}CCVVpA>z6dAWh6Bxj-n6DA>vr<4Gf+xuX31b5%zfB?7-M z(jUO?xY)$N--;6V@XpC`yzsb(UQ$y$BilkmfJLJzzo=y^B2eOZBRch+>m^Edk2#kY z-6V0p5<&=Z{TV{66W8ylzAFCKQQSjBw?G_vr22|8E)M0T8-Jo@+kI@W!o=bt4tS+< zUv22-h2ob-@w*Nz`{QGeh7%Au9yGX#(Mwp?5j#3PCRasfUXR2TcUjJ(tVY^073rgA zCodm*73uLj6U!g!wZx1WdE&OH@Q>J~8@V;=^AE;m+Ty{FIMCW5iaz>7G%~#2I%OTv zMI_-PI+eqrY5?xji{d{$7ffb?_k7SX%Q~X#jTF~tl^0K51bQQvb;J%_#=S?3dyE6S zPf&$CTzYd;rKb@cGREueqwnvHZc{E_sq~P1B+YqjR)H_Z1LIMR{#Gtu+WE8mr`-xg z)BB3ATh>&gEsuNdxxPRB*gLYLz(8iW0OY2Z*FZ9NvU}>W%DfhA=oi(F47*ZP%SOra zEF^zMG%bwoV&v55lz&>YuA<3PyzR@2BQJ-t?VoE3884@Rde6|A^6c}M*erwD*JRF& z?rL_&0(}^NNbC1sIakgyPQV)^ZrUC8>`m_U^_eQSVvV68(sGb(V-he&i=Gu%{DmQO z{j!`tnlHGFdNA_kc6zpP*4t?3juVMf=XKR1#_LNnVz+=f#-_Vu8X|eQoHBj;xu@8So3*A~!!vRylf}Re!gD-#9 z7_t})H}W*|;)OvU~+=;xkd@C1ki@nF@w0SeX0+Mei{ACwV#1%8cc<23H-l zRNe?HD|$OQxSQE5Sn)FPg5+Om1p@piKAhRg9oh>wh-MASZQ!48 z?+Whg@^9NX3H<2>%~ z=6tYu!MF|*zn8^U`Px$B*=6>QwsRI-TjGW}ST1k{!npkgJMOX8oW9fYlvo&;f38k< zaTx4YZ@KYM^~260C6#vZmb`H+n>tjum&0(Ca!3q7pJCpxKH!he#Id94bL782-P8D3z^%ea2EmDCc;=eKu^le?Z zXj%o`e3jVdjXmIqD%i8^5bLX8hn6X>(&w0{Pao&1@HsCw)d%&3xDN2Uxzc!Ie!WoN z#UV4-49y#3Kf;IaPToj-EwgXiJIC^Tj5{mhNLB1Ma>Vtj7$QYiiC?QalvB&B5~sYe ze%2QCt09p_qD?i2Ft-h>j7`_#GP}MjKY1UY>3J;%4^@Z7wra@cN8%RkuE373(!T2y zHLl}~+I*`EQZCnKDJpIDvis5aGz<>{~c*C?;q*r&SXLDR*9N!rB z;!Jgik|CYf8c#@ZKpSE|SiVM~y%GJtLf{6-1)gYQ>Fb0sV#9bopq8}UISavmf~&=hkDh_gkM^3B>dpR zlwnIgKlKTs$D0qH{TvoSH4(kbdqyMl==5n=-ygHg*IC$tj*B@QK7Wm>vCIQGBe(qJT zS378{nt?F5q{!mF2_K%H4D5O2@Ur6XSXCt%4F8f-+7eN;7Pd|q(0O&!r^v(a>$WV7 zV~#ao*l3oGqDd`>a(*w!5x%R#!-W^5i&qKsX(eVMa<@M*K*mmsLoiS-ipvD!R+fqs zool~1MP4pMuopxqOWI2Gt^MK@rNL=27X}zd8vqroTa9;>*MB>`xpu>13)zHdK_pRr ztN03;z~3nKtmEL~?rpgbQ`U0-Hc>^ZPVdGRkuO(u6IZv3+l)$H_1dVB$-nh>hXG>Z zo9}N#WF4gWK#Z${(a?T}VYkxvLWkvRvi6d?=|4yH?(o^j9a`@bwDvjyUZ3pteF|w^# z#hcS-t@o6L!?-dgPNVY6TG4ss+$SelzdxTh|2%`nENaf0#yp-omo^f&yisVqwM$pl zN7B-^#we`!*{a0zL$>n^AhJ7#i!dMbYp+8_e_b`<+5N?%n*{ReCvJS;C0lcm4g>sk z;Kj6i4R$3_%$c)XT(@^0n~`Vtx;P61zj-j==&n_$&$KU=9=MWcuogbNTG;34)H*l6 zKlwP%XPeVC1ZxKF5LSpz8Za_8ZlJ}vT1fNJg&*;yHpYA)&QVq$6ZaaRL%feD znD>a*@0^Xj~_saa;`Z zb!ea%cRkdE6JkFC_@$pPs_omiM*O(K{_|emUFD4$Z;K{<4&9o&u9QowU-Ep`h@uTq zE4yI87XjYQ-j3K+sJNPEa8lS|(CDf(nC7@KCZa^+F?j|Lq>odH(0aR-`KR)H9J0il z4IRoga>+7ET<+;ppUx*6Mdw-6lRk$n_gdAwqc+L&v1f@Z2>X8;xz>;wF{@c z#0DM6Ra7I_1IPHfVolS_kNcBFE@v0|KN6Zm4uLu{S22QMYLL1p<8ITsW!FUT1Z>mc zv_ZRM=x|bSdmm3dMvm#P8)uhyy5$!|12=)Nv{Y@UBtY87uU(#fFs8%4DX~qHQ%(l2 zW?B}JrTi?VYn2e+xKJjC7(HR)W>*+YtNV#+=w$yKmYQk{j+LSs%uJ!_p3;8)hE}g< zgI!&JM^HioPmm5jzzmP|_#pg^~?IsKBwdrz(P+Ek;=TMIlUz8-2M4qbO@|G502=}dsASlNw zeglk#5tF19c7zi~nJmOk_^aNKd3US(%#fQ5S12q7cl$1=cOW6RCb7EJW0ehJ@axHd-cAp zGh&Tw9pCqzN~v0~wcqC6c(ft7tl-iamaaH5s$mgE^_0 zP#Z~#t%mjFNwhWpJUQe76_I3a>}+75D$vBH?$1#jKFXR-%$BS0D*^OOAt&9O5~TLa z%kxVK3gIsrDBMGQt;1KyRU=Fmlhf8*;`mbGT&_ev!QU+OhOC4%*^>UFX~Wo=jNV0_(p{XSHiD)%mf1Taqn#00`E60gx4F z0*|l))j;|H`y&??c(1T&_XJ_`p8%@?cLU1+mr1)R5?cXTa2+5k>M2nLvYczkuW%BS z6{T!8KVTxTI`2rW@f?(SfE+~qVn)UK z^>hNRM_YkyIPbDG2s4oQIr~uL3z@G9)gQI0?vh_mbEc$m&*-m?ayKZfX(J$OT18@heX?tbq^mCGfF2r~$Qf>1 zS>LQqip1eSc25@|ySb4>A0WHAD3CMiX^1%k69&eN9FBbUI?V7IAgA;KAjWjE<)29i zBfq5N@59V?_5)E02B#6O2#i9QQ+g}HtbTojSpbLs?)s zpax_`erRY`JRvrIOzg-co9!DAoYH-V4H-5PHLvlq*+El)T%0}zGCmQQ4_FS!argi& zW=DQ1@wCJvK-#H_nQjll8p6YeC5-BukYF1VVWuAf%%>O2Q#5I8D>J7|^aU+PC&c>2 z4IMS)tJdb!IRj+VdqtYA@oQr?tvSMU*%PFvi%P+cCGSN(tl4HDYr0s5lcUW1=OApf zcuWktJjvE~NJ1jD;fN@L1byTC4QAL{MxS3^2evm|GoXW9_0UvS@cCu=+2z!^np(@P zx%^_8eSV#`mRoDRwl)xJ-R>zX+D^8tF_5DxP=+63KVWB{dBq$=?%?UEncd6^e-7ki z`;z+x#z69d?j|uA$b`Lmn4@H*6+Ieuh(Ts*={;CXubbzl-#t4$B8 z;Q9e(22GI>dB;H0W$y!75kDYn{tl2W@`D}w-eZvI;=?GG(`FNp4OszX#p8w!j2#&_ zDxnn`R21Q0iS>FWnHgFxi$}ZIf~s&CJyQzE3`P&_7uPq=X8RC*#tQrxZ^rLIm@Cm5 zw5}X54af=|A7)l$GmsS*60NZ{1z}cjND>q#m@&f4@CJ|tdHo1?1q$4`hXVz>W)T3t6$+Ko;ZyY{k4y*8iq4p1x-?;;+1H;T%zQ=z*?`0FFvGsdrl_amnNGPikM<12nWv=y!5Ks(kNuWg43qjxNRqgAj~eP1Z2fMU|$;OJ>8r&1%b@h z74ht%o-)7RkzYw3v(ll^Wyy$O#%o?P3;NP9z`=2X+2YlRFOT^4GfbQeo&~>`Vix!X z;wvKDXV};To(+y7Tn6D&GM~|-<1id;wz&vb06zs-0NzO+2!(wZB@G=wx~?jaHJ_Ji zW;_naidvqyiwcxQxWXKB*WLi6eet4G|y2E?bXqC4^s-*dNFSJXm5js0G3t zWBx$q?+N6XdYTCTvw$m0P1j}sS-?j~$QqY^+f1MVnZai89CUMJ{7@hZY9qtnB%1aU z6mtn41Y}pX26A0V7~OYdOhT+}_e#?eg_i$@FjvuKrRIBH<*&^R<>vJAfg zbVmm3kdPS;7&5F6?FYPT_UQ>A3)rt;cQ26?y~gZ_%4^M3!(&Db^cx&IuDi6l4W8?3 zeB98u;V}sb?ZGqr&N?&QPe6{G2+7Z0Z^Xc9}vsu9uAoDG}#q6(#o6RAgjWGEe2p8j|9vPc3Y{-~c z+v#+(LyrJi(+_}bnTO1%`ZhC+!%=KPUz;rtc*a`?rUxrz#{*e`aWUK?Y_?knGe4j0 zrbjL#%yj1v&WAyjJe<9Up9~P317~4C4;+$)J7oqPcbFOX85z?T4Oz&8$@2wQ%&4x;s8$3?fMoFGGD`;QukbIXx~rrS0FxvDQV!%4~0 zBr#kXyduLbfy}6`J=2T;_)%V{f6}!WNUWR^jmT8?3YT%%n`I0h#{Q( zHINOND*4!iKBMD?B=xfm&N2-<0$DSML?4MoB|gbC3%(8H0JtFWki<x@M%=uB{hR52z z!?@*?%O7n{v3ys|__jdis{l2iiy-o!%=e60|Aqqq5nV+-zb)RaUBi(Gah(C?pi&&;*LbNC6$4ILb&{L{A z><8enFg?{LRPoU@Ux(6756ADf^%P%+`b|Flgm0MAO4n*Ql<9gne(%>)YB;oFaCaCo zR`kL(g6!=P8l*@1hT5+}>uG4A=+G91HW^wcL;D0;lpfh7)Lsosa2ws-J5*Z$t+$a= zbZjuHG|MN<-Uxlu#7Hp(S_ebB0Id^K*lS>J^ft6j(E2b|EmT;K@C~zfz)IJeQOY}d zRxO7;3QmjHvulOgGoZECQ-d40Vt_R^sAygtOml><{*0lx>`znO%>)GhsZ>#FF8iXl+dR7C6(ofeKI<%eD zY_>+ms5w_FNPCD-ON2E2oNtiU+S_IeMhJ6hWU#J7h$dl59X+LqLrK)L@OzuCHFenUpqe3iWRp;P)!H(N zeF8MN5!sxE)=+nE7;4`Xz}izQT9-AYHZn&$m!s7RWc3-Fnxox@*3ICW*R!;DpmjC4 zM>$%%AXcAg*Fo!OXisvq_HcB2a@xDl=v`GW>>Z@Jg_t21J-SAa60C=}bSSg*l$H*y zd?@mRT|qs%Yp`2RAgzC}D*_$$)Rv+4N)4DRwJ|wb7PM$`TB(K@MrIE4gIy75MOt}Z z&x&x^&pNmQWJiSBOJjVrF|*m8a;SG&=(D22;LWTkhcZ{!q8-|2FPrPI zO;3XvHVklGF%?i>^aOpsMAzCm)Gu1<5$(d1vU*B8htgHgYUj`ng4-C>$5aOnpM~_D159?Vltlmz~*Ck9DtcQ1T zXiKp7p?K!hE=aqH5J#wyuU4`>I?@b91!<`W!Q+TaYZ+w!2%%1TcJ~HK2mM6XFlD^1 zz2eZaI+!hi4LY)DM`O?H-oO<~Pd&R^12-tW%{nY&8`(e{E z3Ok44I6~L@V{-J~$ZXfK{Kc@>&qnQY?`l>N> z_A-vADeVa~j!q*3E`b9b8lSzePI92f1}WS1tby1C2I%?X!nAe+F$fHEI9NM{5IWu* zk6LBy^mKtSa_z$riqcb~L+uBkbg# zG!~9^p>>e97$K}%bR_o7tGPo;c|Cjxcchdd4*UBKk0a;l?EJ4b^&3 zGs-#FFxVA=)`r0^&{|T{93#-rMnkaGC=2wI;STL5aGVJ?{oKGHrLL}xaM(wTGzKJ6 z9E8SkfSMyQDq!t~TG=fpA8 zB-wPbv2tj05n|Pm4$ZoU5Y|dL#~M$z;<(1Vrl%x2)E_77ClbT7m?^n4*^CgK?5d~5 z25FBF>R^PpHbhS4`j?#;YTpiRprMtSMpuB-21DaGfghuTlpT83c!#~kbdHwn@u6sL z_yiO=U7s}}OiP$y7J~Nn4YFq-gxwffxW8t)+h~EkFG6N<2ccn+MZv{pN?RpX51;6; zFGU!S5n~&;8cHfM>5*cdEO4kE9HeC;#NIVrWG_F9XRXu;p1wSNVzi4j{Z)fkLpLbVT| z^)pMsQd4TS*+}ClQ5mjhO?GHUz+r7RN(gqFgR@IcaYqos28(Q}%{3P@yRiw6*0re) zZ7VpgPlhM8?-1f1U|4A0Y39hmFvmVS2%#v%8C$rv8zD|lBV>1b6ECP(X)FO*(;UiG zU7PN(SJjOpB^osu8V;+|L+$&Z;SdVWYo77Q0d5eqP{t}3^pqJ6t@Qkyvjw)$D1^A+ zxN=n57a|m6 zDx0kyLaut@ra@XqglZs!K5rPLtwPA$8o8z4m6n($-a+=ttBvJzAhsK5P0SqdK(GoS zE)K>E1NF&j{X|-rHuhb!gOL=&>^wru7NZ-BP|-DJD6g^qjz_4DQ30OM-ebskk8lkd z-ZJ0}S$l2n#V!G%VAx~cVT;(HYxA(tuGJ&vg=zll%&5HF3$*?SaTaJi)M=X$YGD@B zBiL;{77+w^CJc5(puV1p^T;x2Y#01ADM^CwQOjeU}dhvl{T8qb~AE_ks-JQ4tg6Qvn)F35<<)!W%&lF#Wv}) z7C78CS)-P1dmAA(0H=+xAngW1bg#w}oK|788Omd<_`MKf&ggBpYmu%kc4#%Wn7e?Y z;}IK2x)g9ETNp+8uEHVPPIs+HXZ2c|cL-=-OKj?PG-5 z);vb~;B8I^2Wc}9s%zv;-|d!hd02P#$u>P=X_yxHzPa>Zhk>)FBE(9YmB~P;7D6~# z;*G!^gyQLCBnuTr>hQ%n+)Mv%+8*LQf)YB|`MAdB%E(5Qm(( z6*bwHlM6~1j}Xg4L*5E@L!hw{z=*NmLI^JyG4k-Rxz=G%*^kN?Zw8O*S!*5I?+9}i z8)H`sJ7BJs=uUWR7D8c&!}Abk>cye#8?H3OkTQxNAL)^Z}y$TK6H?&hZT=~Nm zHyoO2cLW;Fv9K$2#ER_=4bOexHbKKq-6>SF9ku2Jd$wM?dN0Wf@+TDu;b>l4%`a+H;?Q2Hiy}#WEq?9=1}`NXpy=* zw6Z5GZE%jZGe@(ZDKS3_as`_P&jW{(UldrJe?GZr;9N6UoPRL{n9NB*qxjoeW6SPP}YjV!g=HzHsax|Y0*>I*91ufjrGIF#$A6Z=69BpBac0EU{{;_43 zkfZH|h9_0z>~h}HA`C5icLUcP>LN5eqQk7r1*5$?8n_w?sWmy;cb2Br{>0oSu|Q&_ z>x)ngj2H9qAitE~5hxYnKb28)68|(QwggEZ8+jI!R+n%hw4*P4Dj1i1y zs4t<(35->vw4RdT(0YApo*6LS+6AfmKh^W?3$qu$j3KPM@53PkT0LZl0f^|im-Q3- z!qj7z^?duolwx}Leuvuaiau+9nEKWg{RH6b6+Pd9Fh$eD4>+`lt0)RA`^~E<8 z?k}(j%>I_)AMDr{l~4ehc&wb!jA{&l^_IovMpmbuMe*BEoT%uJkC#wvx_f5lXtHBuVMMB9B_>KfH?kAQgD1W!?Iub) z1NDuWCG=C7g_3A7S;pi>HglTfUxbX}-8lXb>84pgrb`8~chi9QXM0of^MNc+NL-}g z3JVPu^Or=bOYn;sybWZ68gkOyR9q0yhMeR5p^8?8jkXRVV3Y3uXWr57UqKvNs zH}#P3SiSEzeVCUAfvY97wvaTK11^R8HpbvD|!G(yQB2KCzNYI z{Ih*7@e3e3@-|6{+=YHYm{TAhN@oTIf%$=@fLxnu0cqC|_!2N&Vn-nJ>jC5sky$78 zk_3@8>kY)o$;KPJt7QCYAPd+i!<&HkXG_N~R`dfI-UVbiyMf3r z$;PYsG&m>?j!1)}rh)CajelGbhfQAQw_-DH#`Fj%Y1Km)* z?GY5#=vN>s@>HTLN+WLvlF!G|C6dpNFb@tMk|!dmt%SrJG9{@FrgxB-wEhXI++A^9efZzi#YjDJ~%!-34VwG6ibCUG8i zkVGdSUD*SOf3_I>;@}zrWI^#HCDOH{5hgVbznD)Fkh3HeNc-86p9f^Y0*HUMB{ICs z75-<$av8B2$O5-WgKd)E0c6GY0XeEq09oK!pf~WL#PV<-^Q#1;eRUu!;w!NRkQJ{B zWcvDc_`fQY-je7KV zf-lQ-S7rQFb})ZPFvHJ)%-{a3F4$!UK7OHS|IGTKxB*PS|GaA2C<#Q8KKk$;Z_GkY;6$dL^_F`B#PAm@sWtB z17ctRh@&LVi^2gQO4bE2Isn8c;vk7k5>@JgxFkl@1(6sC;yj7VqCy}D?|LAn2ZFdN z&XKrCBCsBa&&1?m+WHSQ-rCD{(6r#KI5|ks%U;QVxj}YBauZSu@MNr zMj(C@;~IhRZVcioiO0gHF^G#KQX7MKDlUb}J8pK5ssjWej6PHNLj0Djz5<~@&5(y%> z4Tw7=Dv6LbAa0Ua+6F`waf`&lwjd(gf~Y1IwFS{K3dCa)J|a8{#6uDrqd@qIMIhybyR#J~<9%5(q`DB?PR zDA^H2Hi;nN*%3r0iHRLSgorE>iJd_BbpjD8#&rVW-5JDH5)Fk}35SK{I z>;j@;7Z8m_N*55pT|wL-(Nu(V1#y$a(ykzyi(4cXz5*ih6%a3rMX!Kp*$u>F65%4e z8;FM_Hg*HiN<1R5wmXO(-9bc(b=^U9=>fvM2Z*+!YYz}^JwarUh!$E;5IcJ+1;o&v zN`%s0>>@F+7c9#3f<;FW*9%0+-XO9`bQYey@hg+W#NHsfiYyX|F(CY6Ky(x1VnBHJ z0dbW?58=}X#6=RReL(aQmq^U)3!-6P5HTX9FNolNAnuUpD?<8#xJhDZKM=9v7Kw$i zAR=Qy3=oTALA2}-;xUOh5#ArfLlPVNgBUCxkytwbM2`U=;>EfFAi4|$;XV+=Fwu1& z2)8&886-vsEe^y^67g{$62vYN0|$X9GYG_J5jO}#$-yA9NsJYqgF$4Hm^c{3IFUsn zaR>;%As{A*aYI0O$Ah>^VxsVg2XT=^YCMP}af!stp&%L#1u&a1fEhLA)jw4F}P31c=8ZQbhO&5D!Ug90B5W@rcCQksx}E1d%G% zjResp0fc)3h&iHb0tmNJATmg#32hXJoh0H%fzZV+5(7trC^H(wd=WPqM9DEAvPlTx zIR-=~iHT!CED~8H632q@8w+BI7&jJ#cOrh%&OUHv)EpCxmH~~cD1Q2V)q6r{cz6#>@ER8AO?6 z5ZgsuGKi9sL1dHIAv`C8$RsgwGKgIwi$vlS5PnlY>=EOpfbgCQ;wp&@;WHJ)MG~n~ zLF^ZoNX(oDqTw_U2Sv&>5W&+y+#zvTgiHr1gh)W`iL}Drkzf=&H#kf=u z-m^hmC2>{w%m#6hMCxo1pNUH(X3hc8a1My;B4rMU;JF~~khmd2=7P9MV(DBEUx`~J z7N&uSOat+aSd<2$<(nWLlei_q-vsfH#Kt#4d@CN2SgV8Rp@X<1*6AR+%md**55zsu zbsh+}`5-b#+!xw>5Iaf4&j;~9>>@F60f;gSK>Q@)7Jw)zKxC8nMR*DjnIt9(5RXI_ ziNu8<{1$@vO^jQpbXI;B=LnC5&mu%$T!iS#^9ZjxBK1cY7OBC+r-5Rq?z$SW4T1)}9r5RXY{B77+d%O{o-@{31=mqhe3fV)^n zC?J%#0R=@@LLre(C@i#h07XO$p{UqJ@DPQU1B!__fGD{FIb<(KAti+83J{qj{8obS z6ysKcNL&fxDhV&)vkHXwDiEowK$H=eKqzHJ?bU#CA_X93u155o)rhVjLf!>mQKS(n ziCcuqqVXC)6|sm=Roo|36X9zC-eNhyM?4}_7t!kgzG5ArhEUc6{6trRzep$46xs$r zEfGVgEp`#=h{Epy0z@34t~f{t6rLMVxsn@ExrrN5xge26B9nyQCJ-TF+$I>-7v~6} z!e=v}ftXBaC@vAgMC~mAhe#nb64wchMMyfJiAW`Si~0b|5ELnrqhI z;f&ppdPS+JDw#Vnt|?Pp>~~k=CaQ=zsW@oWdyR2dCV9|~v@eu3s(n;CF3d4@0AsB` z7x?Oq^skhaiv9Xd#O^@s4MjA#rA$Mn2X86)S)O=!TNz@1?Rbs>xu`peo0I!R5gqO+ zrnvQI&H4_B^XK0d;Aik7F74QNPkBYLm%WBh>sg({J3ha!lpC8baUkPy zZ*GXFAE!E!o;)>w$njG-KDF~gAAhVcQxr59eBn*n@RR@QlH=uS+VCUqI+Eiblx1GL ztKcCoU(}HtgT+iP$rd0V(|!$0HhL5$>rB*)82eIyqm)AH8h z0?Apoy4kQN%mhEY;m!N=#_hu6l58Lo^UfqM#WQ_Fa7;)K?3Y})wBwEBrr_A}2+0+O z&Pyu%;jM6bfnUtwACu7s#PSS`xG)M~4MC_06=%~?J#H1jGae~g>vKz=FaUCDKncKmWcHR-fYk}D0a z5p@1^mRuR=A0lJ=iFedlMp?*3dX!{WNtT2DsWf~=a^=BYm0UM)_-CsCxh6SYc8AJ8 z0c$%9g^uqjxk}KhGZH_&Bv%=FQOWh@{axx+AXDLS_C|~}tO}i1$=DmbNlzzMgYdd8 zdxMwVnbsT9NOI#O=L3${dD$7`CCBkkju*MC3-Z1w-&P+aU2ffyuK_(B99?eRnD>Lu z#4L=r=UK2nWFNYYKfES}f3}*C1CrxydfL^39Fp7=aO|<#5Z($6V<^q7gABhi02n+_$3>Dp{W_9 zIiv;TWk|TFFhZ^5!!P&DfV>8&58-#l3PJc?GJaQ&-#yz5Oc(JZR9}Ct9FrkqA&HQ2 zkge#gZIJgNyg1ky(go5rkJvpzZBv1_8M(UDhw%0)@8({FT!Op}c}LV9sg_8xAzFdB zK=MHHLU@VwC&(|5yAa;Gy#?WITwdM10^wEbsgUWAei*U+A%h^i`@J7>0CEs=2yz&5 z6mlH06S5b=g$D~wl5HK7u@GK+_lNM_ePu{_h&!Yxgx^lM4OtIa4Y%}y^oI0-^o4MB zdK>P42l6gtErd%sS8lGR9`n{NuLl1H;oaqtXe_Vs*MRO1;dcv$qUd3e{t$d&CCSFG%}j%EAK|{i zZK4f?+W?pSc95o!<`DiV_m?3pA^f5izt1)fG88f!G6%wM{slooAitxK$B-wGrw}d} z3WVPw;%)Ceys5nx$}Y%GNIHbq=6P}Y34|A%M^>ol+3^oI<8z1Sj;3la>e0GWnNd0E;4X#{C3 z8jn?5MOgXs`zREKDb%@lWI~QX_^ktO8{8%sws^O(C>KAC(u{3_Tb<#^SkQeT>w3bqG$rwyTgU5PTEdhLg4J7~}|~E9CHwqlxNayK+cGPgIMF zxs%l1WsZX9c{m%G34vd1aJ%g!Buf-dQj2z_dICZ-Ml)>br=atIK<-n>hmcDUE-5&( z+dhGuhcIh=2j9r&Bj8!cX(RjEOnL@lWtlsB>dcbKSq0jiGm`neDB1H#bCMZJG)6O* z3r4Wa^VYd5kZG8R-pOid{y7FDnVgIbg`TFD>0!%L)IXQuFM#M2uMmD`oBMJ>NC60!NAA&iA$G`9gstV0t#VZ`{P{I<7YNr-uAL0$ zf#id@$}o+&$>)ELV}{&sXiFO_4S9EjSpgQv3J}>SHinfh4!sx)@Df#~sO3D%LM;Qn zGK6P%9yC26WkioDYRxK@5nTyFsv?llJhYaEaJeWCDJRmA6W-#So1#|pg?KYVKAt7UwIjPg4gQl_)Qwr>g
    Sl!rg~kCclXN24t2}k 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 HiY!L}pKlIt diff --git a/compose.local.yml b/compose.local.yml index 9405755f..6c0048fa 100644 --- a/compose.local.yml +++ b/compose.local.yml @@ -16,7 +16,7 @@ services: environment: MINIO_ROOT_USER: ${S3_USER} MINIO_ROOT_PASSWORD: ${S3_PASSWORD} - MINIO_DEFAULT_BUCKETS: ${S3_NAME} + MINIO_DEFAULT_BUCKETS: ${S3_BUCKETS} volumes: - ./data/s3:/bitnami/minio/data ports: diff --git a/compose.yml b/compose.yml index 3d5fac3e..c3b5c90a 100644 --- a/compose.yml +++ b/compose.yml @@ -24,7 +24,7 @@ services: environment: MINIO_ROOT_USER: ${S3_USER} MINIO_ROOT_PASSWORD: ${S3_PASSWORD} - MINIO_DEFAULT_BUCKETS: ${S3_NAME} + MINIO_DEFAULT_BUCKETS: ${S3_BUCKETS} volumes: - ./data/s3:/bitnami/minio/data networks: diff --git a/package.json b/package.json index 5de800fd..3064e5cb 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-form": "^0.34.1", "@tanstack/react-query": "^5.59.16", + "@tanstack/react-query-next-experimental": "^5.63.0", "@tanstack/zod-form-adapter": "^0.34.1", "@trpc/client": "^11.0.0-rc.599", "@trpc/react-query": "^11.0.0-rc.599", diff --git a/src/app/[locale]/(default)/page.tsx b/src/app/[locale]/(default)/page.tsx index 24855542..03ff63fc 100644 --- a/src/app/[locale]/(default)/page.tsx +++ b/src/app/[locale]/(default)/page.tsx @@ -9,7 +9,7 @@ export default async function HomePage({ }) { const { locale } = await params; setRequestLocale(locale); - const hello = await api.test.helloWorld(); + const hello = api.test.helloWorld(); return (

    {hello}

    diff --git a/src/app/api/data/[trpc]/route.ts b/src/app/api/data/[trpc]/route.ts index 4dc57003..ca921ee4 100644 --- a/src/app/api/data/[trpc]/route.ts +++ b/src/app/api/data/[trpc]/route.ts @@ -1,6 +1,7 @@ import { env } from '@/env'; import { router } from '@/server/api'; import { createContext } from '@/server/api/context'; +import { getLocaleFromRequest } from '@/server/api/locale'; import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import type { NextRequest } from 'next/server'; @@ -9,7 +10,7 @@ function handleRequest(req: NextRequest) { endpoint: '/api/data', req, router, - createContext, + createContext: () => createContext(getLocaleFromRequest(req)), onError: env.NODE_ENV === 'development' ? ({ path, error }) => { diff --git a/src/components/providers/TRPCProvider.tsx b/src/components/providers/TRPCProvider.tsx index 4011ee4e..631cb7a6 100644 --- a/src/components/providers/TRPCProvider.tsx +++ b/src/components/providers/TRPCProvider.tsx @@ -4,7 +4,9 @@ import { env } from '@/env'; import { api } from '@/lib/api/client'; import { createQueryClient } from '@/lib/api/queryClient'; import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; import { httpBatchLink, loggerLink } from '@trpc/client'; +import { useLocale } from 'next-intl'; import { useState } from 'react'; import SuperJSON from 'superjson'; @@ -23,6 +25,7 @@ const getQueryClient = () => { function TRPCProvider(props: { children: React.ReactNode }) { const queryClient = getQueryClient(); + const locale = useLocale(); const [trpcClient] = useState(() => api.createClient({ @@ -35,6 +38,11 @@ function TRPCProvider(props: { children: React.ReactNode }) { httpBatchLink({ transformer: SuperJSON, url: `${env.NEXT_PUBLIC_SITE_URL}/api/data`, + headers() { + return { + 'accept-language': locale, + }; + }, }), ], }), @@ -42,9 +50,11 @@ function TRPCProvider(props: { children: React.ReactNode }) { return ( - - {props.children} - + + + {props.children} + + ); } diff --git a/src/lib/api/server.ts b/src/lib/api/server.ts index 62dd31b4..ed88542d 100644 --- a/src/lib/api/server.ts +++ b/src/lib/api/server.ts @@ -1,17 +1,45 @@ import 'server-only'; import { createQueryClient } from '@/lib/api/queryClient'; +import type { routing } from '@/lib/locale'; import { createCaller, type router } from '@/server/api'; import { createContext } from '@/server/api/context'; import { createHydrationHelpers } from '@trpc/react-query/rsc'; +import { getLocale } from 'next-intl/server'; import { cache } from 'react'; const getQueryClient = cache(createQueryClient); -const caller = createCaller(createContext); -const { trpc: api, HydrateClient } = createHydrationHelpers( - caller, - getQueryClient, -); +const getApiClient = cache(async () => { + const locale = await getLocale(); + const caller = createCaller( + createContext(locale as (typeof routing.locales)[number]), + ); -export { api, HydrateClient }; + return createHydrationHelpers(caller, getQueryClient); +}); + +type ApiClient = Awaited>['trpc']; + +const api = new Proxy({} as ApiClient, { + get(_target, prop: keyof ApiClient) { + return new Proxy( + {}, + { + get(_target, method: string) { + return async (...args: unknown[]) => { + const { trpc } = await getApiClient(); + type Router = ApiClient[typeof prop]; + return ( + trpc[prop][method as keyof Router] as ( + ...args: unknown[] + ) => Promise + )(...args); + }; + }, + }, + ); + }, +}); + +export { api }; diff --git a/src/server/api/context.ts b/src/server/api/context.ts index aa6822ff..e1678a4b 100644 --- a/src/server/api/context.ts +++ b/src/server/api/context.ts @@ -1,15 +1,18 @@ +import type { routing } from '@/lib/locale'; import { auth } from '@/server/auth'; import { db } from '@/server/db'; import { s3 } from '@/server/s3'; type TRPCContext = { + locale: (typeof routing.locales)[number]; db: typeof db; auth: typeof auth; s3: typeof s3; }; -function createContext(): TRPCContext { +function createContext(locale: (typeof routing.locales)[number]): TRPCContext { return { + locale, auth, db, s3, diff --git a/src/server/api/routers/test.ts b/src/server/api/routers/test.ts index 077b59d2..2b074b9e 100644 --- a/src/server/api/routers/test.ts +++ b/src/server/api/routers/test.ts @@ -2,8 +2,8 @@ import { publicProcedure } from '@/server/api/procedures'; import { createRouter } from '@/server/api/trpc'; const testRouter = createRouter({ - helloWorld: publicProcedure.query(async () => { - return 'Hello, World!'; + helloWorld: publicProcedure.query(async ({ ctx }) => { + return ctx.locale; }), }); From d03c6eac7d3ed495ea942989d76b5a8fe51b21a4 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:10:35 +0100 Subject: [PATCH 48/93] feat: triffer revalidation on locale change --- src/components/providers/TRPCProvider.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/providers/TRPCProvider.tsx b/src/components/providers/TRPCProvider.tsx index 631cb7a6..fc9a3fb1 100644 --- a/src/components/providers/TRPCProvider.tsx +++ b/src/components/providers/TRPCProvider.tsx @@ -7,6 +7,7 @@ import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; import { httpBatchLink, loggerLink } from '@trpc/client'; import { useLocale } from 'next-intl'; +import { useEffect } from 'react'; import { useState } from 'react'; import SuperJSON from 'superjson'; @@ -27,6 +28,11 @@ function TRPCProvider(props: { children: React.ReactNode }) { const queryClient = getQueryClient(); const locale = useLocale(); + // biome-ignore lint/correctness/useExhaustiveDependencies: locale is needed to trigger revalidation on language change + useEffect(() => { + queryClient.invalidateQueries(); + }, [locale, queryClient]); + const [trpcClient] = useState(() => api.createClient({ links: [ From 0650f9f25002bf1d3713f53b1c1b628b72ad97d3 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:21:48 +0100 Subject: [PATCH 49/93] chore: update example --- src/server/api/routers/test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/api/routers/test.ts b/src/server/api/routers/test.ts index 2b074b9e..654d6922 100644 --- a/src/server/api/routers/test.ts +++ b/src/server/api/routers/test.ts @@ -3,7 +3,8 @@ import { createRouter } from '@/server/api/trpc'; const testRouter = createRouter({ helloWorld: publicProcedure.query(async ({ ctx }) => { - return ctx.locale; + // Dette er bare et eksempel, vi vil selfølgelig bruke getTranslations istedet + return ctx.locale === 'no' ? 'Hei Verden!' : 'Hello World!'; }), }); From 99154ef18e5b6f8450f40cc858934f0f7880aac0 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:24:56 +0100 Subject: [PATCH 50/93] feat: update drizzle --- bun.lockb | Bin 271004 -> 271476 bytes package.json | 5 +++-- src/server/auth/user.ts | 12 ++++++++++++ src/server/db/tables/skills.ts | 10 +++++----- src/server/db/tables/users.ts | 23 ++++++++++++++++++++++- 5 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 src/server/auth/user.ts diff --git a/bun.lockb b/bun.lockb index b101116eb6900046d2b527bdbd4625bb82e55e8b..bd947c074c8d19a5a3c204b2c0e5d3b21eca37ed 100755 GIT binary patch delta 47006 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˂Zq3i*)7F__sWiKk1x(AgD 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 XFY0W7s(GT!GCg_{lj`&viY!L}-xr;w delta 46756 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

    2(*a+YQ+LuW?+dA+(m3R{hA$f z5z~o<)4lAO<$$Y#GVPh=fmA1uQuGD3u0b{F?a=Ba;j0xSBY=ERcr=QK^8FVvxtB8i zBt$h-6Q={Syp_ofNDMM*x5iIf&hy3t$Ok0~{+lz}e@{ETHyp^90`eK&TzPuxysd$N zJ;W)|irbSMm_IXbPjF%0>oYxiBC{wnu*oyMcp`Jf_J0$Zn@U;0`;lANS-82TyRBzd zm?p%+zI~e@OQ#^$3`JmY1HlT#>7L3g%2HCIs)CKm!Dl*lFS{gBeo^NJRGvp^dY3ZG FD*$~+#`*vN diff --git a/package.json b/package.json index 3064e5cb..a64bdb7c 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "country-flag-icons": "^1.5.13", "cva": "^1.0.0-beta.1", "date-fns": "^4.1.0", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "^0.38.3", + "drizzle-zod": "^0.6.1", "framer-motion": "^11.11.11", "lucide-react": "^0.396.0", "next": "^15.0.1", @@ -80,7 +81,7 @@ "autoprefixer": "^10.4.20", "client-only": "^0.0.1", "cross-env": "^7.0.3", - "drizzle-kit": "^0.24.2", + "drizzle-kit": "^0.30.1", "drizzle-seed": "^0.3.0", "fluid-tailwind": "^1.0.3", "lefthook": "^1.8.1", diff --git a/src/server/auth/user.ts b/src/server/auth/user.ts new file mode 100644 index 00000000..76ff3cbd --- /dev/null +++ b/src/server/auth/user.ts @@ -0,0 +1,12 @@ +import { eq } from 'drizzle-orm'; + +import type { TRPCContext } from '@/server/api/context'; +import { users } from '@/server/db/tables'; + +async function getUserFromEmail(email: string, context: TRPCContext) { + return await context.db.query.users.findFirst({ + where: eq(users.email, email), + }); +} + +export { getUserFromEmail }; diff --git a/src/server/db/tables/skills.ts b/src/server/db/tables/skills.ts index 86df66ef..606ddd11 100644 --- a/src/server/db/tables/skills.ts +++ b/src/server/db/tables/skills.ts @@ -18,15 +18,15 @@ const usersSkills = pgTable( 'users_skills', { userId: integer('user_id') - .references(() => users.id) + .references(() => users.id, { onDelete: 'cascade' }) .notNull(), skillId: integer('skill_id') - .references(() => skills.id) + .references(() => skills.id, { onDelete: 'cascade' }) .notNull(), }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.skillId] }), - }), + (table) => { + return [primaryKey({ columns: [table.userId, table.skillId] })]; + }, ); const skillsRelations = relations(skills, ({ many }) => ({ diff --git a/src/server/db/tables/users.ts b/src/server/db/tables/users.ts index 714df7c1..586d3064 100644 --- a/src/server/db/tables/users.ts +++ b/src/server/db/tables/users.ts @@ -12,9 +12,22 @@ import { const users = pgTable('users', { id: serial('id').primaryKey(), + createdAt: timestamp('created_at', { + withTimezone: true, + mode: 'date', + }) + .notNull() + .defaultNow(), username: varchar('username', { length: 8 }).unique().notNull(), + firstName: varchar('first_name', { length: 30 }).notNull(), + lastName: varchar('last_name', { length: 30 }).notNull(), + birthDate: timestamp('birth_date', { + withTimezone: true, + mode: 'date', + }).notNull(), + phoneNumber: varchar('phone_number', { length: 20 }).unique().notNull(), passwordHash: text('password_hash').notNull(), - name: varchar('name', { length: 256 }).notNull(), + email: varchar('email', { length: 254 }).unique().notNull(), }); const usersRelations = relations(users, ({ many }) => ({ @@ -32,6 +45,13 @@ const sessions = pgTable('session', { }).notNull(), }); +const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { + fields: [sessions.userId], + references: [users.id], + }), +})); + type SelectUser = InferSelectModel; type InsertUser = InferInsertModel; type SelectSession = InferSelectModel; @@ -41,6 +61,7 @@ export { users, usersRelations, sessions, + sessionsRelations, type SelectUser, type InsertUser, type SelectSession, From 2d9670e407f659d4baebf0cccddb72f2645a7514 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:42:47 +0100 Subject: [PATCH 51/93] feat: finish feide route --- src/app/api/auth/feide/route.ts | 43 +++++++++++++++++++++++++++++---- src/server/auth/feide.ts | 19 ++++++++++++++- src/server/auth/session.ts | 14 ++++------- src/server/auth/user.ts | 17 ++++++++++--- src/server/db/tables/users.ts | 11 ++++----- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index d1fe0045..d754beb5 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -1,5 +1,14 @@ import { env } from '@/env'; -import { validateFeideAuthorization } from '@/server/auth/feide'; +import { + type FeideUserInfo, + validateFeideAuthorization, +} from '@/server/auth/feide'; +import { + createSession, + generateSessionToken, + setSessionTokenCookie, +} from '@/server/auth/session'; +import { createUser, getUserFromEmail } from '@/server/auth/user'; import { cookies } from 'next/headers'; import { type NextRequest, NextResponse } from 'next/server'; @@ -11,6 +20,8 @@ export async function GET(request: NextRequest) { const cookieStore = await cookies(); const storedState = cookieStore.get('feide-state')?.value; const codeVerifier = cookieStore.get('feide-code-verifier')?.value; + cookieStore.delete('feide-state'); + cookieStore.delete('feide-code-verifier'); if (!code || !state || !storedState || !codeVerifier) { return NextResponse.json(null, { status: 400 }); @@ -29,9 +40,31 @@ export async function GET(request: NextRequest) { Authorization: `Bearer ${tokens.accessToken}`, }, }); - const userInfo = await userInfoResponse.json(); - console.log(userInfo); - cookieStore.delete('feide-state'); - cookieStore.delete('feide-code-verifier'); + if (!userInfoResponse.ok) { + return NextResponse.json(null, { status: 500 }); + } + + const userInfo: FeideUserInfo = await userInfoResponse.json(); + let user = await getUserFromEmail(userInfo.email); + + if (!user) { + const username = userInfo.email.split('@')[0]; + + if (!userInfo || !username) { + return NextResponse.json(null, { status: 500 }); + } + + user = await createUser(username, userInfo.name, userInfo.email); + + if (!user) { + return NextResponse.json(null, { status: 500 }); + } + } + + const sessionToken = generateSessionToken(); + const session = await createSession(sessionToken, user.id); + await setSessionTokenCookie(sessionToken, session.expiresAt); + + return NextResponse.redirect(new URL('/', request.url)); } diff --git a/src/server/auth/feide.ts b/src/server/auth/feide.ts index 5fed75b3..f9fa005f 100644 --- a/src/server/auth/feide.ts +++ b/src/server/auth/feide.ts @@ -15,6 +15,19 @@ const feideOAuthClient = new OAuth2Client( }, ); +type FeideUserInfo = { + aud: string; + sub: string; + 'connect-userid_sec': string[]; + 'dataporten-userid_sec': string[]; + 'https://n.feide.no/claims/userid_sec': string[]; + 'https://n.feide.no/claims/eduPersonPrincipalName': string; + name: string; + email: string; + email_verified: boolean; + picture: string; +}; + async function createFeideAuthorization() { const state = generateState(); const codeVerifier = generateCodeVerifier(); @@ -59,4 +72,8 @@ async function validateFeideAuthorization(code: string, codeVerifier: string) { } } -export { createFeideAuthorization, validateFeideAuthorization }; +export { + createFeideAuthorization, + validateFeideAuthorization, + type FeideUserInfo, +}; diff --git a/src/server/auth/session.ts b/src/server/auth/session.ts index 0293f26a..c36b5714 100644 --- a/src/server/auth/session.ts +++ b/src/server/auth/session.ts @@ -1,5 +1,5 @@ import { env } from '@/env'; -import type { TRPCContext } from '@/server/api/context'; +import { db } from '@/server/db'; import { sha256 } from '@oslojs/crypto/sha2'; import { encodeBase32LowerCaseNoPadding, @@ -17,23 +17,19 @@ function generateSessionToken(): string { return token; } -async function createSession( - token: string, - userId: number, - context: TRPCContext, -) { +async function createSession(token: string, userId: number) { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: InsertSession = { id: sessionId, userId, expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), }; - await context.db.insert(sessions).values(session); + await db.insert(sessions).values(session); return session; } -async function invalidateSession(sessionId: string, context: TRPCContext) { - await context.db.delete(sessions).where(eq(sessions.id, sessionId)); +async function invalidateSession(sessionId: string) { + await db.delete(sessions).where(eq(sessions.id, sessionId)); } async function setSessionTokenCookie(token: string, expiresAt: Date) { diff --git a/src/server/auth/user.ts b/src/server/auth/user.ts index 76ff3cbd..ec046f62 100644 --- a/src/server/auth/user.ts +++ b/src/server/auth/user.ts @@ -1,12 +1,21 @@ +import { db } from '@/server/db'; import { eq } from 'drizzle-orm'; -import type { TRPCContext } from '@/server/api/context'; import { users } from '@/server/db/tables'; -async function getUserFromEmail(email: string, context: TRPCContext) { - return await context.db.query.users.findFirst({ +async function getUserFromEmail(email: string) { + return await db.query.users.findFirst({ where: eq(users.email, email), }); } -export { getUserFromEmail }; +async function createUser(username: string, name: string, email: string) { + const [user] = await db + .insert(users) + .values({ username, name, email }) + .returning(); + + return user; +} + +export { getUserFromEmail, createUser }; diff --git a/src/server/db/tables/users.ts b/src/server/db/tables/users.ts index 586d3064..76516ade 100644 --- a/src/server/db/tables/users.ts +++ b/src/server/db/tables/users.ts @@ -19,15 +19,14 @@ const users = pgTable('users', { .notNull() .defaultNow(), username: varchar('username', { length: 8 }).unique().notNull(), - firstName: varchar('first_name', { length: 30 }).notNull(), - lastName: varchar('last_name', { length: 30 }).notNull(), + name: varchar('name', { length: 60 }).notNull(), + email: varchar('email', { length: 254 }).unique().notNull(), birthDate: timestamp('birth_date', { withTimezone: true, mode: 'date', - }).notNull(), - phoneNumber: varchar('phone_number', { length: 20 }).unique().notNull(), - passwordHash: text('password_hash').notNull(), - email: varchar('email', { length: 254 }).unique().notNull(), + }), + phoneNumber: varchar('phone_number', { length: 20 }).unique(), + passwordHash: text('password_hash'), }); const usersRelations = relations(users, ({ many }) => ({ From e7f3b6cb6be54a785508895cefbce0c4246927c6 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:12:49 +0100 Subject: [PATCH 52/93] feat: add auth query --- src/app/api/auth/feide/route.ts | 6 ++++++ src/components/layout/Header.tsx | 2 ++ src/components/layout/header/ProfileMenu.tsx | 1 - src/server/api/routers/auth.ts | 6 ++++++ src/server/auth/matrix.ts | 1 - 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index d754beb5..91abee4c 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -60,6 +60,12 @@ export async function GET(request: NextRequest) { if (!user) { return NextResponse.json(null, { status: 500 }); } + + const sessionToken = generateSessionToken(); + const session = await createSession(sessionToken, user.id); + await setSessionTokenCookie(sessionToken, session.expiresAt); + + return NextResponse.redirect(new URL('/create-account', request.url)); } const sessionToken = generateSessionToken(); diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 76afd32e..288454f1 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -4,10 +4,12 @@ import { LocaleMenu } from '@/components/layout/header/LocaleMenu'; import { MobileSheet } from '@/components/layout/header/MobileSheet'; import { Nav } from '@/components/layout/header/Nav'; import { ProfileMenu } from '@/components/layout/header/ProfileMenu'; +import { api } from '@/lib/api/server'; import { useTranslations } from 'next-intl'; function Header() { const t = useTranslations('layout'); + const { user } = api.auth.auth(); return (

    diff --git a/src/components/layout/header/ProfileMenu.tsx b/src/components/layout/header/ProfileMenu.tsx index 9504a666..a691f495 100644 --- a/src/components/layout/header/ProfileMenu.tsx +++ b/src/components/layout/header/ProfileMenu.tsx @@ -9,7 +9,6 @@ import { } from '@/components/ui/DropdownMenu'; import { Link } from '@/lib/locale/navigation'; import { UserIcon } from 'lucide-react'; -import * as React from 'react'; function ProfileMenu({ t }: { t: { profile: string; signIn: string } }) { return ( diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 3816f518..a689c34b 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -7,6 +7,8 @@ import { createFeideAuthorization } from '@/server/auth/feide'; import { TRPCError } from '@trpc/server'; import { cookies, headers } from 'next/headers'; +import { sanitizeAuth } from '@/server/auth'; + const ipBucket = new RefillingTokenBucket(5, 60); const authRouter = createRouter({ @@ -40,6 +42,10 @@ const authRouter = createRouter({ return url.href; }), + auth: publicProcedure.query(async ({ ctx }) => { + const result = await ctx.auth(); + return sanitizeAuth(result); + }), }); export { authRouter }; diff --git a/src/server/auth/matrix.ts b/src/server/auth/matrix.ts index 65bfbdd7..f6b6b5ae 100644 --- a/src/server/auth/matrix.ts +++ b/src/server/auth/matrix.ts @@ -2,7 +2,6 @@ import { env } from '@/env'; import { hmac } from '@oslojs/crypto/hmac'; import { SHA1 } from '@oslojs/crypto/sha1'; -// Get unique nonce async function getNonce(): Promise { const getRequest: RequestInfo = new Request( `${env.MATRIX_ENDPOINT}/v1/register`, From a4efee7b195fa09fed11c5cbf1f9c51e42fb0ee0 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:58:38 +0100 Subject: [PATCH 53/93] feat: auth works --- src/app/[locale]/(default)/page.tsx | 2 +- src/components/layout/Header.tsx | 9 +++++---- src/components/layout/header/ProfileMenu.tsx | 19 ++++++++++++++++--- src/lib/api/server.ts | 5 +++++ src/server/db/seed.ts | 3 ++- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/app/[locale]/(default)/page.tsx b/src/app/[locale]/(default)/page.tsx index 03ff63fc..24855542 100644 --- a/src/app/[locale]/(default)/page.tsx +++ b/src/app/[locale]/(default)/page.tsx @@ -9,7 +9,7 @@ export default async function HomePage({ }) { const { locale } = await params; setRequestLocale(locale); - const hello = api.test.helloWorld(); + const hello = await api.test.helloWorld(); return (

    {hello}

    diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 288454f1..665071cb 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -5,11 +5,11 @@ import { MobileSheet } from '@/components/layout/header/MobileSheet'; import { Nav } from '@/components/layout/header/Nav'; import { ProfileMenu } from '@/components/layout/header/ProfileMenu'; import { api } from '@/lib/api/server'; -import { useTranslations } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; -function Header() { - const t = useTranslations('layout'); - const { user } = api.auth.auth(); +async function Header() { + const t = await getTranslations('layout'); + const { user } = await api.auth.auth(); return (
    @@ -48,6 +48,7 @@ function Header() { }} /> - diff --git a/src/lib/api/server.ts b/src/lib/api/server.ts index ed88542d..10fafb78 100644 --- a/src/lib/api/server.ts +++ b/src/lib/api/server.ts @@ -42,4 +42,9 @@ const api = new Proxy({} as ApiClient, { }, }); +// async function api() { +// const { trpc } = await getApiClient(); +// return trpc; +// } + export { api }; diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index a597eeec..d932a720 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -26,8 +26,9 @@ async function main() { console.log('Inserting user...'); const user: InsertUser = { - name: 'Frank Sinatra', username: 'fransin', + name: 'Frank Sinatra', + email: 'frank@sinatra.com', passwordHash: await hashPassword('Password1!'), }; From 66f5789e8c9d7b3f2f404d98b2fbf455a7573c51 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:01:39 +0100 Subject: [PATCH 54/93] chore: remove commented code --- src/lib/api/server.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/api/server.ts b/src/lib/api/server.ts index 10fafb78..ed88542d 100644 --- a/src/lib/api/server.ts +++ b/src/lib/api/server.ts @@ -42,9 +42,4 @@ const api = new Proxy({} as ApiClient, { }, }); -// async function api() { -// const { trpc } = await getApiClient(); -// return trpc; -// } - export { api }; From 2674c639ad04bb557d4918c6bac1f2bee3738de4 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:29:58 +0100 Subject: [PATCH 55/93] feat: add signout route --- messages/en.json | 4 +++ messages/no.json | 4 +++ src/components/layout/header/ProfileMenu.tsx | 21 ++++++++++++--- src/server/api/procedures.ts | 28 +++++++++++++++++++- src/server/api/routers/auth.ts | 23 +++++++++++++--- 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/messages/en.json b/messages/en.json index a1bd925c..7212c94e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -191,5 +191,9 @@ } } } + }, + "api": { + "tooManyRequests": "Too many requests, please try again later", + "notAuthenticated": "Not authenticated" } } diff --git a/messages/no.json b/messages/no.json index dffe67fa..06e5e4e2 100644 --- a/messages/no.json +++ b/messages/no.json @@ -191,5 +191,9 @@ } } } + }, + "api": { + "tooManyRequests": "For mange forespørsler. Vennligst vent noen minutter og prøv igjen", + "notAuthenticated": "Ikke autentisert" } } diff --git a/src/components/layout/header/ProfileMenu.tsx b/src/components/layout/header/ProfileMenu.tsx index 8a855626..de0d29ed 100644 --- a/src/components/layout/header/ProfileMenu.tsx +++ b/src/components/layout/header/ProfileMenu.tsx @@ -7,6 +7,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/DropdownMenu'; +import { api } from '@/lib/api/client'; import { Link } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; import { UserIcon } from 'lucide-react'; @@ -16,10 +17,13 @@ type ProfileMenuProps = { t: { profile: string; signIn: string; + settings: string; + signOut: string; }; }; function ProfileMenu({ hasUser, t }: ProfileMenuProps) { + const signOutMutation = api.auth.signOut.useMutation(); return ( @@ -33,9 +37,20 @@ function ProfileMenu({ hasUser, t }: ProfileMenuProps) { - - {t.signIn} - + {hasUser ? ( + <> + + {t.settings} + + signOutMutation.mutate({})}> + {t.signOut} + + + ) : ( + + {t.signIn} + + )} ); diff --git a/src/server/api/procedures.ts b/src/server/api/procedures.ts index c99e8e4f..5d4d08d0 100644 --- a/src/server/api/procedures.ts +++ b/src/server/api/procedures.ts @@ -1,4 +1,6 @@ import { trpc } from '@/server/api/trpc'; +import { TRPCError } from '@trpc/server'; +import { getTranslations } from 'next-intl/server'; const timingMiddleware = trpc.middleware(async ({ next, path }) => { const start = Date.now(); @@ -18,4 +20,28 @@ const timingMiddleware = trpc.middleware(async ({ next, path }) => { const publicProcedure = trpc.procedure.use(timingMiddleware); -export { publicProcedure }; +const authMiddleware = trpc.middleware(async ({ next, ctx }) => { + const t = await getTranslations({ + locale: ctx.locale, + namespace: 'api', + }); + const { user, session } = await ctx.auth(); + + if (!session) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: t('notAuthenticated'), + }); + } + return next({ + ctx: { + ...ctx, + user, + session, + }, + }); +}); + +const authenticatedProcedure = publicProcedure.use(authMiddleware); + +export { publicProcedure, authenticatedProcedure }; diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index a689c34b..0f8e7dfc 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,8 +1,16 @@ import { env } from '@/env'; -import { publicProcedure } from '@/server/api/procedures'; +import { + authenticatedProcedure, + publicProcedure, +} from '@/server/api/procedures'; import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket'; import { createRouter } from '@/server/api/trpc'; import { createFeideAuthorization } from '@/server/auth/feide'; +import { + deleteSessionTokenCookie, + invalidateSession, +} from '@/server/auth/session'; +import { getTranslations } from 'next-intl/server'; import { TRPCError } from '@trpc/server'; import { cookies, headers } from 'next/headers'; @@ -12,14 +20,19 @@ import { sanitizeAuth } from '@/server/auth'; const ipBucket = new RefillingTokenBucket(5, 60); const authRouter = createRouter({ - signInFeide: publicProcedure.mutation(async () => { + signInFeide: publicProcedure.mutation(async ({ ctx }) => { + const t = await getTranslations({ + locale: ctx.locale, + namespace: 'api', + }); + const headerStore = await headers(); const clientIP = headerStore.get('X-Forwarded-For'); if (clientIP !== null && !ipBucket.check(clientIP, 1)) { throw new TRPCError({ code: 'TOO_MANY_REQUESTS', - message: 'Rate limit exceeded. Please try again later.', + message: t('tooManyRequests'), }); } @@ -46,6 +59,10 @@ const authRouter = createRouter({ const result = await ctx.auth(); return sanitizeAuth(result); }), + signOut: authenticatedProcedure.query(async ({ ctx }) => { + await invalidateSession(ctx.session.id); + await deleteSessionTokenCookie(); + }), }); export { authRouter }; From d8d5c124ccf5effc02a096e1962d16baa1ec9830 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:11:16 +0100 Subject: [PATCH 56/93] feat: implement sign out --- messages/en.json | 2 ++ messages/no.json | 2 ++ src/app/[locale]/settings/page.tsx | 15 +++++++++++++++ src/components/layout/Header.tsx | 4 +++- src/components/layout/header/ProfileMenu.tsx | 11 ++++++++--- src/lib/locale/index.ts | 4 ++++ src/server/api/routers/auth.ts | 10 +++++----- 7 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 src/app/[locale]/settings/page.tsx diff --git a/messages/en.json b/messages/en.json index 7212c94e..d4479ef5 100644 --- a/messages/en.json +++ b/messages/en.json @@ -81,6 +81,8 @@ "system": "System", "profile": "Profile", "signIn": "Sign in", + "signOut": "Sign out", + "settings": "Settings", "links": "Links", "utilities": "Utilities", "haveYouFoundA": "Have you found a", diff --git a/messages/no.json b/messages/no.json index 06e5e4e2..867c7c49 100644 --- a/messages/no.json +++ b/messages/no.json @@ -81,6 +81,8 @@ "system": "System", "profile": "Profil", "signIn": "Logg inn", + "signOut": "Logg ut", + "settings": "Innstillinger", "links": "Lenker", "utilities": "Verktøy", "haveYouFoundA": "Har du funnet en", diff --git a/src/app/[locale]/settings/page.tsx b/src/app/[locale]/settings/page.tsx new file mode 100644 index 00000000..1e21b52b --- /dev/null +++ b/src/app/[locale]/settings/page.tsx @@ -0,0 +1,15 @@ +import { api } from '@/lib/api/server'; + +export default async function SettingsPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { user } = await api.auth.state(); + return ( +
    +

    Settings

    +

    {JSON.stringify(user, null, 2)}

    +
    + ); +} diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 665071cb..d536cbfb 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -9,7 +9,7 @@ import { getTranslations } from 'next-intl/server'; async function Header() { const t = await getTranslations('layout'); - const { user } = await api.auth.auth(); + const { user } = await api.auth.state(); return (
    @@ -52,6 +52,8 @@ async function Header() { t={{ profile: t('profile'), signIn: t('signIn'), + signOut: t('signOut'), + settings: t('settings'), }} />
    diff --git a/src/components/layout/header/ProfileMenu.tsx b/src/components/layout/header/ProfileMenu.tsx index de0d29ed..0f234084 100644 --- a/src/components/layout/header/ProfileMenu.tsx +++ b/src/components/layout/header/ProfileMenu.tsx @@ -8,7 +8,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/DropdownMenu'; import { api } from '@/lib/api/client'; -import { Link } from '@/lib/locale/navigation'; +import { Link, useRouter } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; import { UserIcon } from 'lucide-react'; @@ -23,7 +23,12 @@ type ProfileMenuProps = { }; function ProfileMenu({ hasUser, t }: ProfileMenuProps) { - const signOutMutation = api.auth.signOut.useMutation(); + const router = useRouter(); + const signOutMutation = api.auth.signOut.useMutation({ + onSuccess: () => { + router.refresh(); + }, + }); return ( @@ -42,7 +47,7 @@ function ProfileMenu({ hasUser, t }: ProfileMenuProps) { {t.settings} - signOutMutation.mutate({})}> + signOutMutation.mutate()}> {t.signOut} diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index ae07f59c..44ef1ab4 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -38,6 +38,10 @@ const routing = defineRouting({ en: '/auth/success', no: '/autentisering/success', }, + '/settings': { + en: '/settings', + no: '/instillinger', + }, '/events': { en: '/events', no: '/arrangementer', diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 0f8e7dfc..bbdd613f 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -20,6 +20,10 @@ import { sanitizeAuth } from '@/server/auth'; const ipBucket = new RefillingTokenBucket(5, 60); const authRouter = createRouter({ + state: publicProcedure.query(async ({ ctx }) => { + const result = await ctx.auth(); + return sanitizeAuth(result); + }), signInFeide: publicProcedure.mutation(async ({ ctx }) => { const t = await getTranslations({ locale: ctx.locale, @@ -55,11 +59,7 @@ const authRouter = createRouter({ return url.href; }), - auth: publicProcedure.query(async ({ ctx }) => { - const result = await ctx.auth(); - return sanitizeAuth(result); - }), - signOut: authenticatedProcedure.query(async ({ ctx }) => { + signOut: authenticatedProcedure.mutation(async ({ ctx }) => { await invalidateSession(ctx.session.id); await deleteSessionTokenCookie(); }), From 189725814d226b86e768133669c56c4ddbc6ddaa Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:25:10 +0100 Subject: [PATCH 57/93] fix: typo and new password page placement --- messages/en.json | 2 +- messages/no.json | 2 +- .../auth/create-account/password/page.tsx | 15 +++++++++++++++ src/components/layout/header/ProfileMenu.tsx | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/app/[locale]/auth/create-account/password/page.tsx diff --git a/messages/en.json b/messages/en.json index d4479ef5..1ee4c6fd 100644 --- a/messages/en.json +++ b/messages/en.json @@ -43,7 +43,7 @@ "home": "Home", "signIn": "Sign in", "useYourAccount": "Use your account", - "forgotPassword": "Forgot password?", + "forgotPassword": "Forgot password", "submit": "Submit", "form": { "username": { diff --git a/messages/no.json b/messages/no.json index 867c7c49..6c5ddbf4 100644 --- a/messages/no.json +++ b/messages/no.json @@ -43,7 +43,7 @@ "home": "Hjem", "signIn": "Logg inn", "useYourAccount": "Bruk din konto", - "forgotPassword": "Glemt passord?", + "forgotPassword": "Glemt passord", "submit": "Send", "form": { "username": { diff --git a/src/app/[locale]/auth/create-account/password/page.tsx b/src/app/[locale]/auth/create-account/password/page.tsx new file mode 100644 index 00000000..7d3b6b1c --- /dev/null +++ b/src/app/[locale]/auth/create-account/password/page.tsx @@ -0,0 +1,15 @@ +import { setRequestLocale } from 'next-intl/server'; + +export default async function CreateAccountPasswordPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + return ( +
    + This page is for adding info to account after first login +
    + ); +} diff --git a/src/components/layout/header/ProfileMenu.tsx b/src/components/layout/header/ProfileMenu.tsx index 0f234084..d4c074d4 100644 --- a/src/components/layout/header/ProfileMenu.tsx +++ b/src/components/layout/header/ProfileMenu.tsx @@ -35,7 +35,7 @@ function ProfileMenu({ hasUser, t }: ProfileMenuProps) {
    ); diff --git a/src/components/providers/AnimatePresenceProvider.tsx b/src/components/providers/AnimatePresenceProvider.tsx index d116b745..57b7948a 100644 --- a/src/components/providers/AnimatePresenceProvider.tsx +++ b/src/components/providers/AnimatePresenceProvider.tsx @@ -1,6 +1,6 @@ 'use client'; -import { AnimatePresence, type HTMLMotionProps, m } from 'framer-motion'; +import { AnimatePresence, m } from 'motion/react'; import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'; import { usePathname } from 'next/navigation'; import { useContext, useRef } from 'react'; @@ -27,10 +27,7 @@ function AnimatePresenceProvider({ return ( - )} - > + {children} diff --git a/src/components/providers/LazyMotionProvider.tsx b/src/components/providers/LazyMotionProvider.tsx index 5edcfeb9..1588ab28 100644 --- a/src/components/providers/LazyMotionProvider.tsx +++ b/src/components/providers/LazyMotionProvider.tsx @@ -1,4 +1,4 @@ -import { LazyMotion, domAnimation } from 'framer-motion'; +import { LazyMotion, domAnimation } from 'motion/react'; function LazyMotionProvider({ children }: { children: React.ReactNode }) { return {children}; From 94f9acfd5d5a357b5c790ea39069cb2230655aa0 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:05:49 +0100 Subject: [PATCH 63/93] dep: update dependencies --- bun.lockb | Bin 369686 -> 368869 bytes package.json | 4 +- src/app/[locale]/layout.tsx | 29 ++++--- src/components/ui/Calendar.tsx | 15 ++-- src/components/ui/Command.tsx | 2 +- src/components/ui/Dialog.tsx | 44 +++++----- src/components/ui/DropdownMenu.tsx | 13 +-- src/components/ui/Input.tsx | 2 +- src/components/ui/Popover.tsx | 2 +- src/components/ui/ScrollArea.tsx | 132 +++++++++++++++++++++-------- src/components/ui/Select.tsx | 9 +- src/components/ui/Sheet.tsx | 14 +-- src/components/ui/Tooltip.tsx | 2 +- tailwind.config.ts | 9 +- 14 files changed, 172 insertions(+), 105 deletions(-) diff --git a/bun.lockb b/bun.lockb index 44213291bc8d2a5f4bedbea9b3ee9df91ac355f7..6314a6bc9d3a88d0c7ec42d679d275f2edb6bdee 100755 GIT binary patch delta 58361 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 K_1m_^P5%uTgQ*Ju delta 58777 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

    Ks}@lWA{oE$#moe|}Memn8ZMm@XL z@aA`YBELIGDGcnv*h&Ew>xKzp22CF_FMaX5%`ODt*&WaB-!6Ii*5c3hgyWeV4>?@; z@~1;bfNs~g0GqZB1 z?~I+M6#b#{|GR~Dn`~VlYNYB*O8bfegNHZ0mv5Lt9cCI%P{wkDbK3^gW)02yyK+_u M-dQ)(a3S!&0WD*mj{pDw diff --git a/package.json b/package.json index fed08bd1..cffa8fe4 100644 --- a/package.json +++ b/package.json @@ -88,11 +88,9 @@ "lefthook": "^1.8.1", "postcss": "^8.4.47", "server-only": "^0.0.1", - "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.14", "tailwindcss-animate": "^1.0.7", - "tailwindcss-radix": "^3.0.5", "typescript": "^5.6.3" }, - "packageManager": "bun@1.1.12" + "packageManager": "bun@1.1.29" } diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index b3310ba1..c2bd5c8d 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,4 +1,5 @@ import { RootProviders } from '@/components/providers/RootProviders'; +import { ScrollArea } from '@/components/ui/ScrollArea'; import { Toaster } from '@/components/ui/Toaster'; import { routing } from '@/lib/locale'; import { cx } from '@/lib/utils'; @@ -66,27 +67,31 @@ export async function generateMetadata({ }; } -export default async function LocaleLayout(props: LocaleLayoutProps) { - const params = await props.params; - - const { locale } = params; - - const { children } = props; - +export default async function LocaleLayout({ + params, + children, +}: LocaleLayoutProps) { + const { locale } = await params; setRequestLocale(locale); return ( -

    - {children} - -
    + +
    + {children} + +
    +
    diff --git a/src/components/ui/Calendar.tsx b/src/components/ui/Calendar.tsx index 47517e98..980c49c6 100644 --- a/src/components/ui/Calendar.tsx +++ b/src/components/ui/Calendar.tsx @@ -15,9 +15,6 @@ import { type DropdownProps, } from 'react-day-picker'; -import { dayPickerLocales } from '@/lib/locale'; -import { cx } from '@/lib/utils'; - import { Button, buttonVariants } from '@/components/ui/Button'; import { ScrollArea } from '@/components/ui/ScrollArea'; import { @@ -28,7 +25,10 @@ import { SelectValue, } from '@/components/ui/Select'; -export type CalendarProps = DayPickerProps; +import { dayPickerLocales, type routing } from '@/lib/locale'; +import { cx } from '@/lib/utils'; + +type CalendarProps = DayPickerProps; /** * This component is also customised a lot from its shadcn counterpart. @@ -36,6 +36,7 @@ export type CalendarProps = DayPickerProps; * Our version supports a dropdown for the month and year if enabled via the captionLayout prop. * Also it uses the correct locale labels for everything based on the current locale. */ + function Dropdown({ value, onChange, @@ -83,7 +84,7 @@ function Calendar({ }: CalendarProps) { const t = useTranslations('ui'); const format = useFormatter(); - const currentLocale = useLocale(); + const locale = useLocale(); return ( { let label = format.dateTime(date, { @@ -209,4 +210,4 @@ function Calendar({ } Calendar.displayName = 'Calendar'; -export { Calendar }; +export { Calendar, type CalendarProps }; diff --git a/src/components/ui/Command.tsx b/src/components/ui/Command.tsx index 411130f2..da5fb0a3 100644 --- a/src/components/ui/Command.tsx +++ b/src/components/ui/Command.tsx @@ -116,7 +116,7 @@ const CommandItem = forwardRef< , React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)); +>(({ className, children, ...props }, ref) => { + const t = useTranslations('ui'); + return ( + + + + {children} + + + {t('close')} + + + + ); +}); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index 3a7bb6e3..7fea396d 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = forwardRef< svg]:size-4 [&>svg]:shrink-0', inset && 'pl-8', className, )} @@ -99,7 +100,7 @@ const DropdownMenuCheckboxItem = forwardRef< ( & + VariantProps & { + viewPortClassName?: string; + scrollBarClassName?: string; + scrollBar?: boolean; + orientation?: 'vertical' | 'horizontal'; + }; const ScrollArea = forwardRef< React.ComponentRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - {children} - - - - -)); + ScrollBarProps +>( + ( + { + className, + viewPortClassName, + scrollBarClassName, + orientation, + scrollBar = true, + variant, + children, + ...props + }, + ref, + ) => ( + + + {children} + + {scrollBar && ( + + )} + + + ), +); ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; const ScrollBar = forwardRef< React.ComponentRef, - React.ComponentPropsWithoutRef ->(({ className, orientation = 'vertical', ...props }, ref) => ( - - - -)); + React.ComponentPropsWithoutRef< + typeof ScrollAreaPrimitive.ScrollAreaScrollbar + > & + VariantProps & { + thumbClassName?: string; + } +>( + ( + { className, thumbClassName, variant, orientation = 'vertical', ...props }, + ref, + ) => ( + + + + ), +); ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; export { ScrollArea, ScrollBar }; diff --git a/src/components/ui/Select.tsx b/src/components/ui/Select.tsx index af0fafff..57e9fdbe 100644 --- a/src/components/ui/Select.tsx +++ b/src/components/ui/Select.tsx @@ -74,9 +74,9 @@ const SelectContent = forwardRef< - + - {children} )); diff --git a/src/components/ui/Sheet.tsx b/src/components/ui/Sheet.tsx index 18431ec3..58e89a33 100644 --- a/src/components/ui/Sheet.tsx +++ b/src/components/ui/Sheet.tsx @@ -20,7 +20,7 @@ const SheetOverlay = forwardRef< >(({ className, ...props }, ref) => ( {children} - + {t('close')} diff --git a/src/components/ui/Tooltip.tsx b/src/components/ui/Tooltip.tsx index ecb80e61..7b902e6b 100644 --- a/src/components/ui/Tooltip.tsx +++ b/src/components/ui/Tooltip.tsx @@ -18,7 +18,7 @@ const TooltipContent = forwardRef< ref={ref} sideOffset={sideOffset} className={cx( - 'fade-in-0 zoom-in-95 rdx-state-closed:fade-out-0 rdx-state-closed:zoom-out-95 rdx-side-bottom:slide-in-from-top-2 rdx-side-left:slide-in-from-right-2 rdx-side-right:slide-in-from-left-2 rdx-side-top:slide-in-from-bottom-2 z-50 animate-in rdx-state-closed:animate-out overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md', + 'fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 animate-in overflow-hidden rounded-md bg-primary px-3 py-1.5 text-primary-foreground text-xs data-[state=closed]:animate-out', className, )} {...props} diff --git a/tailwind.config.ts b/tailwind.config.ts index 59add9b7..82d97520 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,8 +1,6 @@ import tailwindFluid, { extract, screens, fontSize } from 'fluid-tailwind'; -import tailwindScrollbar from 'tailwind-scrollbar'; import type { Config } from 'tailwindcss'; import tailwindAnimate from 'tailwindcss-animate'; -import tailwindRadix from 'tailwindcss-radix'; import { fontFamily } from 'tailwindcss/defaultTheme'; const config: Config = { @@ -78,12 +76,7 @@ const config: Config = { }, }, }, - plugins: [ - tailwindFluid, - tailwindAnimate, - tailwindScrollbar({ nocompatible: true }), - tailwindRadix({ variantPrefix: 'rdx' }), - ], + plugins: [tailwindFluid, tailwindAnimate], }; export default config; From bbc5fdf5ec4100018f5b4aafe440aacf0bd877ac Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:17:49 +0100 Subject: [PATCH 64/93] docs: small updates to contributing --- CONTRIBUTING.md | 50 ++++++++++++++++++--------------------- src/server/api/context.ts | 4 ++-- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 917fd380..5e151d23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,8 +6,6 @@ Make sure you have Bun installed on your machine. If you don't have it, you can download it [here](https://bun.sh/docs/installation). Here is some information about Bun in Next.js: [Build an app with Next.js and Bun](https://bun.sh/guides/ecosystem/nextjs). -If you can't install Bun, you can always use [Node.js](https://nodejs.org/en/) with the `npm` command instead, but it will not be as fast as Bun. - First, install dependencies: ```bash @@ -19,7 +17,7 @@ Also, setup environment variables by copying the `.env.example` file to `.env` a Then, run the development server: ```bash -bun --bun dev +bun dev --turbo ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. @@ -31,13 +29,13 @@ When you build the project, you pre-render all the Server Side Generated (SSG) p You can build the project with the following command: ```bash -bun --bun run build +bun run build ``` To serve the build locally, run: ```bash -bun --bun run start +bun run start ``` ### Check linting and formatting @@ -48,12 +46,6 @@ To check linting and formatting you run the respective command: bun lint ``` -If you are using vscode and are experiencing issues with types, you can restart the typescript server by pressing `cmd + shift + p` and then type `TypeScript: Restart TS Server` (You need to have a typescript file open for this to work). - -You can also try restarting the whole editor by pressing `cmd + shift + p` and then type `Developer: Reload Window`. - -On windows you can use `ctrl` instead of `cmd`. - ## Commit messages We are using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. This is to ensure that we have a consistent way of writing commit messages to make it easier to understand what has been changed and why. Try to follow the guidelines as closely as possible. You can also use [the recommended vscode extension](.vscode/extensions.json) to help you write the commit messages. @@ -63,6 +55,11 @@ We are using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0. - To keep the code as consistent as possible use functions for react components or hooks instead of const variables with arrow function syntax. An exception is when using the forwardRef hook or when creating compound components. - Only use default export for pages or layouts etc. since it is required by Next.js. For everything else use named exports. This is to make it easier to find the components in the codebase or change them without ending up with different names for the same component. - Use `type` instead of `interface` for typescript types. This is to keep the code consistent and to make it easier to read. Also `type` is more flexible than `interface` since it can be used for unions and intersections. +- When using custom icons that are not provided by lucide, make sure to add them as React Components (SVGs) to the `components/assets/icons` folder. This improves performance since the icons are handled as vectors and not as images. +- For internationalization use the `useTranslations` and `getTranslations` (for async components) hooks from `next-intl`. + - For client components pass the translations as props using the `t` prop for consistency. + - On the backend when returning error messages, you can just return the key to the translation as the error message and it will be formatted correctly. + - In the backend context (ctx) the locale is stored in `ctx.locale` and translations can be accessed using `ctx.getTranslations()`. ### Naming conventions @@ -89,7 +86,6 @@ Here is a list of documentations that will help you contribute to the project: - [Tailwind CSS](https://tailwindcss.com/docs) - Styling library - [Fluid for Tailwind](https://fluid.tw/#basic-usage) - Fluid scale utility breakpoints - [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate) - Animation utility classes - - [tailwind-scrollbar](https://github.com/adoxography/tailwind-scrollbar) - Customize scrollbar with tailwind - [Class Variance Authority](https://beta.cva.style/) - Tool for creating style variants in our UI components - [shadcn/ui](https://ui.shadcn.com/docs) - Reusable UI components - [Radix UI Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) - Primitives library that shadcn/ui is built on, great documentation if you need to access the underlying components @@ -107,29 +103,29 @@ Here is a list of documentations that will help you contribute to the project: ### Infrastructure - [Docker](https://docs.docker.com/get-started/) - Containerization tool for the application, database and storage -- [Colima](https://github.com/abiosoft/colima) - Container runtime for docker, I recommend this over Docker Desktop because of performance and license - [Docker Compose](https://docs.docker.com/compose/) - Tool for running multi-container applications +- [Colima](https://github.com/abiosoft/colima) - Container runtime for docker, I recommend this over Docker Desktop because of performance and license - [nginx](https://nginx.org/en/docs/) - Reverse proxy for routing requests to the correct service -### VS Code extensions - -- [Auto Rename Tag](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag) -- [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) -- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) -- [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) - ### Other - [Mozilla](https://developer.mozilla.org/en-US/) - Great resource for looking up documentation for web technologies - [Can I use](https://caniuse.com/) - Check browser support for different web technologies (especially useful for CSS) -## Icons +## Development Environment + +### VS Code + +#### Recommended Extensions + +- [Auto Rename Tag](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag) +- [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) +- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) +- [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) -- When using custom icons that are not provided by lucide, make sure to add them as SVGs to the `components/assets/icons` folder. This improves performance since the icons are handled as vectors and not as images. +#### Issues -## Quirks to keep in mind +- If you are experiencing issues with types, you can restart the typescript server by pressing `cmd + shift + p` and then type `TypeScript: Restart TS Server` (You need to have a typescript file open for this to work). +- You can also try restarting the whole editor by pressing `cmd + shift + p` and then type `Developer: Reload Window`. -- When you want to link to a new internal page use the `` component from `@/lib/navigation` instead of the normal anchortag `
    `. This will ensure that the page is loaded with the correct locale. If you want to link to external resources or other media, use the built-in `` component from Next.js. Remember to add `prefetch={false}` to the `` component if the page is not visited often. - - If you need to use both `` components from `@/lib/navigation` and Next.js, make sure to import the Next.js `` component as `ExternalLink` to avoid naming conflicts. -- Remember to surround Links with the `Button` UI component. This will provide some basic styling and accessibility features for keyboard navigation even if it is not supposed to look like a button. -- For internationalization use the `useTranslations` hook from `next-intl`. For client components you can pass the translations as props. +On windows you can use `ctrl` instead of `cmd`. diff --git a/src/server/api/context.ts b/src/server/api/context.ts index 197b7b99..690c683b 100644 --- a/src/server/api/context.ts +++ b/src/server/api/context.ts @@ -14,7 +14,7 @@ type TRPCContext = { db: typeof db; auth: typeof auth; s3: typeof s3; - t: Translations; + getTranslations: Translations; }; async function createContext( @@ -28,7 +28,7 @@ async function createContext( auth, db, s3, - t, + getTranslations: t, }; } From 5815dee575fd7b4c4b92e86275ad2918aed564a7 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:35:05 +0100 Subject: [PATCH 65/93] fix: lefthook prepare script for windows --- bun.lockb | Bin 368869 -> 368436 bytes package.json | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index 6314a6bc9d3a88d0c7ec42d679d275f2edb6bdee..ea5964e2a5c472952edbd78805da0eb3612fe59d 100755 GIT binary patch delta 59140 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 delta 58639 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%0DhxSX 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 Date: Wed, 15 Jan 2025 06:36:40 +0100 Subject: [PATCH 66/93] feat: add s3 rewrite to next config --- next.config.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/next.config.ts b/next.config.ts index 0c57969b..98612b41 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,11 +1,27 @@ +import { env } from '@/env'; import type { NextConfig } from 'next'; import nextIntl from 'next-intl/plugin'; const withNextIntl = nextIntl('./src/lib/locale/request.ts'); const config: NextConfig = { - reactStrictMode: true, output: 'standalone', + images: { + formats: ['image/avif', 'image/webp'], + }, + async rewrites() { + return { + beforeFiles: [ + { + source: '/s3/:path*', + destination: `http://${env.S3_HOST}:${env.S3_PORT}/:path*`, + basePath: false, + }, + ], + afterFiles: [], + fallback: [], + }; + }, }; export default withNextIntl(config); From 1bfeab387c8af8cf41e86b10835842180176e9dd Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:56:48 +0100 Subject: [PATCH 67/93] chore: update scripts --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 052cdeb5..648eb289 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "scripts": { "prepare": "bun -e \"process.env.NODE_ENV !== 'production' && require('child_process').spawnSync('lefthook', ['install'], {stdio: 'inherit', shell: true})\"", "dev": "next dev", + "dev:setup": "set -e && bun run db:stop && bun run s3:stop && bun run db:delete && bun run s3:delete && bun run db:start && bun run s3:start && sleep 5 && docker compose -f compose.local.yml exec -T db pg_isready && bun run db:push && bun run db:seed", + "dev:stop": "docker compose -f compose.local.yml down", "lint": "biome check --write", "prebuild": "next telemetry disable", "build": "next build", @@ -13,13 +15,17 @@ "start": "drizzle-kit migrate && bun run .next/standalone/server.js", "db:start": "docker compose -f compose.local.yml up db -d", "db:stop": "docker compose -f compose.local.yml down db", + "db:delete": "bun -e \"require('fs').rmSync('data/db', { recursive: true, force: true })\"", + "db:logs": "docker compose -f compose.local.yml logs -f db", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "db:seed": "bun run src/server/db/seed.ts", "s3:start": "docker compose -f compose.local.yml up s3 -d", - "s3:stop": "docker compose -f compose.local.yml down s3" + "s3:stop": "docker compose -f compose.local.yml down s3", + "s3:delete": "bun -e \"require('fs').rmSync('data/s3', { recursive: true, force: true })\"", + "s3:logs": "docker compose -f compose.local.yml logs -f s3" }, "dependencies": { "@aws-sdk/client-s3": "^3.679.0", From 58bd2c7c8a999dfb2e64ce1a9214e33bc3dbe91c Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:17:25 +0100 Subject: [PATCH 68/93] chore: update typogrpahy in globals css --- .../auth/create-account/password/page.tsx | 15 ----------- src/lib/locale/index.ts | 4 --- src/lib/styles/globals.css | 27 +++---------------- 3 files changed, 4 insertions(+), 42 deletions(-) delete mode 100644 src/app/[locale]/auth/create-account/password/page.tsx diff --git a/src/app/[locale]/auth/create-account/password/page.tsx b/src/app/[locale]/auth/create-account/password/page.tsx deleted file mode 100644 index 7d3b6b1c..00000000 --- a/src/app/[locale]/auth/create-account/password/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { setRequestLocale } from 'next-intl/server'; - -export default async function CreateAccountPasswordPage({ - params, -}: { - params: Promise<{ locale: string }>; -}) { - const { locale } = await params; - setRequestLocale(locale); - return ( -
    - This page is for adding info to account after first login -
    - ); -} diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 389032fd..44ef1ab4 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -30,10 +30,6 @@ const routing = defineRouting({ en: '/auth/create-account', no: '/autentisering/opprett-konto', }, - '/auth/create-account/password': { - en: '/auth/create-account/password', - no: '/autentisering/opprett-konto/passord', - }, '/auth/forgot-password': { en: '/auth/forgot-password', no: '/autentisering/glemt-passord', diff --git a/src/lib/styles/globals.css b/src/lib/styles/globals.css index b27bb5ef..d6e24663 100644 --- a/src/lib/styles/globals.css +++ b/src/lib/styles/globals.css @@ -56,39 +56,20 @@ @apply bg-primary/50; } - ::-moz-selection { - @apply bg-primary/50; - } - + /* Typography */ h1 { - @apply scroll-m-20 font-montserrat text-4xl font-extrabold tracking-tight lg:text-5xl; + @apply scroll-m-20 font-marlin text-4xl font-extrabold tracking-tight lg:text-5xl; } h2 { - @apply scroll-m-20 border-b pb-2 font-montserrat text-3xl font-semibold tracking-tight first:mt-0; + @apply scroll-m-20 font-marlin text-3xl font-semibold tracking-tight; } h3 { - @apply scroll-m-20 font-montserrat text-2xl font-semibold tracking-tight; + @apply scroll-m-20 font-marlin text-2xl font-semibold tracking-tight; } h4 { @apply scroll-m-20 font-montserrat text-xl font-semibold tracking-tight; } - - p { - @apply leading-7 [&:not(:first-child)]:mt-6; - } - - blockquote { - @apply mt-6 border-l-2 pl-6 italic; - } - - code { - @apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold; - } - - small { - @apply text-sm font-medium leading-none; - } } From 7736d7469d732cc47ca9c667dc3e32f06cad6896 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:18:38 +0100 Subject: [PATCH 69/93] typo: font --- src/lib/styles/globals.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/styles/globals.css b/src/lib/styles/globals.css index d6e24663..3aa5832b 100644 --- a/src/lib/styles/globals.css +++ b/src/lib/styles/globals.css @@ -58,15 +58,15 @@ /* Typography */ h1 { - @apply scroll-m-20 font-marlin text-4xl font-extrabold tracking-tight lg:text-5xl; + @apply scroll-m-20 font-montserrat text-4xl font-extrabold tracking-tight lg:text-5xl; } h2 { - @apply scroll-m-20 font-marlin text-3xl font-semibold tracking-tight; + @apply scroll-m-20 font-montserrat text-3xl font-semibold tracking-tight; } h3 { - @apply scroll-m-20 font-marlin text-2xl font-semibold tracking-tight; + @apply scroll-m-20 font-montserrat text-2xl font-semibold tracking-tight; } h4 { From 1217367b7f47859330c3e88c5a3f7a68d000a301 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:21:10 +0100 Subject: [PATCH 70/93] i18n: update translation --- messages/no.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/no.json b/messages/no.json index bbac81c9..c2a1c25e 100644 --- a/messages/no.json +++ b/messages/no.json @@ -71,7 +71,7 @@ "hackerspaceHome": "Hackerspace hjemmeside", "navigationMenu": "Navigasjonsmeny", "news": "Nyheter", - "events": "Hendelser", + "events": "Arrangementer", "storage": "Lager", "about": "Om oss", "shiftSchedule": "Vaktliste", From 40c407964f55bd4a63880d3a4439a0ee0162c920 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:29:35 +0100 Subject: [PATCH 71/93] chore: update lighthouse --- .github/workflows/code-quality.yml | 1 + lighthouserc.cjs | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 99176f86..68950ca3 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -39,6 +39,7 @@ jobs: FEIDE_AUTHORIZATION_ENDPOINT: "https://auth.dataporten.no/oauth/authorization" FEIDE_TOKEN_ENDPOINT: "https://auth.dataporten.no/oauth/token" FEIDE_USERINFO_ENDPOINT: "https://auth.dataporten.no/openid/userinfo" + FEIDE_EXTENDED_USERINFO_ENDPOINT: "https://api.dataporten.no/userinfo/v1/userinfo" NEXT_PUBLIC_SITE_URL: "http://localhost:3000" LHCI_TOKEN: ${{ secrets.LHCI_BUILD_TOKEN }} MATRIX_SHARED_SECRET: " " diff --git a/lighthouserc.cjs b/lighthouserc.cjs index 1f06549c..a60b47bb 100644 --- a/lighthouserc.cjs +++ b/lighthouserc.cjs @@ -7,6 +7,7 @@ const config = { collect: { url: [ 'http://localhost:3000/en/', // Trailing slash required, else the regex for default lighthouse rules won't catch this one + 'http://localhost:3000/en/auth', 'http://localhost:3000/en/about', 'http://localhost:3000/en/events', 'http://localhost:3000/en/news', From f6cad7d349de1de3b7444616fe681da9b846aa72 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:57:43 +0100 Subject: [PATCH 72/93] chore: seperate fetches for userinfo --- src/app/api/auth/feide/route.ts | 66 ++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index 489b42fc..c5b5115a 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -37,53 +37,59 @@ export async function GET(request: NextRequest) { return NextResponse.json(null, { status: 500 }); } - const [userInfoResponse, extendedUserInfoResponse] = await Promise.all([ - fetch(env.FEIDE_USERINFO_ENDPOINT, { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }), - fetch(env.FEIDE_EXTENDED_USERINFO_ENDPOINT, { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }), - ]); + const userInfoResponse = await fetch(env.FEIDE_USERINFO_ENDPOINT, { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); - if (!userInfoResponse.ok || !extendedUserInfoResponse.ok) { + if (!userInfoResponse.ok) { return NextResponse.json(null, { status: 500 }); } - const [basicInfo, extendedInfo]: [FeideUserInfo, ExtendedFeideUserInfo] = - await Promise.all([ - userInfoResponse.json(), - extendedUserInfoResponse.json(), - ]); + const userInfo: FeideUserInfo = await userInfoResponse.json(); + if (!userInfo) { + return NextResponse.json(null, { status: 500 }); + } - const userInfo = { - ...basicInfo, - ...extendedInfo, - }; + const username = + userInfo['https://n.feide.no/claims/eduPersonPrincipalName'].split('@')[0]; - if (!userInfo) { + if (!username) { return NextResponse.json(null, { status: 500 }); } - let user = await getUserFromUsername(userInfo.uid[0]); + let user = await getUserFromUsername(username); if (!user) { + const extendedUserInfoResponse = await fetch( + env.FEIDE_EXTENDED_USERINFO_ENDPOINT, + { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }, + ); + + if (!extendedUserInfoResponse.ok) { + return NextResponse.json(null, { status: 500 }); + } + + const extendedUserInfo: ExtendedFeideUserInfo = + await extendedUserInfoResponse.json(); + const date = new Date( - `${userInfo.norEduPersonBirthDate.slice(0, 4)}-${userInfo.norEduPersonBirthDate.slice(4, 6)}-${userInfo.norEduPersonBirthDate.slice(6, 8)}`, + `${extendedUserInfo.norEduPersonBirthDate.slice(0, 4)}-${extendedUserInfo.norEduPersonBirthDate.slice(4, 6)}-${extendedUserInfo.norEduPersonBirthDate.slice(6, 8)}`, ); const emailVerifiedAt = userInfo.email_verified ? new Date() : null; user = await createUser( - userInfo.uid[0], - userInfo.givenName[0], - userInfo.sn[0], + username, + extendedUserInfo.givenName[0], + extendedUserInfo.sn[0], userInfo.email, emailVerifiedAt, date, - userInfo.mobile[0], + extendedUserInfo.mobile[0], ); if (!user) { @@ -94,7 +100,7 @@ export async function GET(request: NextRequest) { const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); - return NextResponse.redirect(new URL('/create-account', request.url)); + return NextResponse.redirect(new URL('/auth/create-account', request.url)); } const sessionToken = generateSessionToken(); From 5eceadb8cbbefcb4937dbbc90f830f29bb61b339 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 03:25:43 +0100 Subject: [PATCH 73/93] i18n: add support for values and format in zod error message translations --- .env.example | 1 + messages/en.json | 8 +-- messages/no.json | 8 +-- src/env.ts | 2 + src/server/api/context.ts | 4 +- src/server/api/errorFormatter.ts | 71 +++++++++++++++++++++ src/server/api/procedures.ts | 7 +- src/server/api/routers/auth.ts | 10 +-- src/server/api/trpc.ts | 27 +------- src/validations/auth/accountSignInSchema.ts | 34 ++++------ src/validations/auth/accountSignUpSchema.ts | 21 ++++++ 11 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 src/server/api/errorFormatter.ts create mode 100644 src/validations/auth/accountSignUpSchema.ts diff --git a/.env.example b/.env.example index c20192e8..39cdd288 100644 --- a/.env.example +++ b/.env.example @@ -39,5 +39,6 @@ FEIDE_USERINFO_ENDPOINT="https://auth.dataporten.no/openid/userinfo" FEIDE_EXTENDED_USERINFO_ENDPOINT="https://api.dataporten.no/userinfo/v1/userinfo" # Matrix +MATRIX_SERVER_NAME=" " MATRIX_ENDPOINT="https://matrix.hackerspace-ntnu.no/_synapse/admin" MATRIX_SECRET=" " diff --git a/messages/en.json b/messages/en.json index 54a3a24f..e2be1f47 100644 --- a/messages/en.json +++ b/messages/en.json @@ -50,15 +50,15 @@ "username": { "label": "Username", "required": "Username is required", - "minLength": "Username must be at least 5 characters", - "maxLength": "Username must be less than 8 characters", + "minLength": "Username must be at least {count} characters", + "maxLength": "Username must be less than {count} characters", "invalid": "Username must contain only letters" }, "password": { "label": "Password", "required": "Password is required", - "minLength": "Password must be at least 8 characters", - "maxLength": "Password must be less than 50 characters", + "minLength": "Password must be at least {count} characters", + "maxLength": "Password must be less than {count} characters", "uppercase": "Password must contain at least one uppercase letter", "specialChar": "Password must contain at least one special character", "confirmLabel": "Confirm password", diff --git a/messages/no.json b/messages/no.json index c2a1c25e..6bb11bd4 100644 --- a/messages/no.json +++ b/messages/no.json @@ -50,15 +50,15 @@ "username": { "label": "Brukernavn", "required": "Brukernavn er påkrevd", - "minLength": "Brukernavn må være minst 5 tegn", - "maxLength": "Brukernavn må være mindre enn 8 tegn", + "minLength": "Brukernavn må være minst {count} tegn", + "maxLength": "Brukernavn må være mindre enn {count} tegn", "invalid": "Brukernavn må kun inneholde bokstaver" }, "password": { "label": "Passord", "required": "Passord er påkrevd", - "minLength": "Passord må være minst 8 tegn", - "maxLength": "Passord må være mindre enn 50 tegn", + "minLength": "Passord må være minst {count} tegn", + "maxLength": "Passord må være mindre enn {count} tegn", "uppercase": "Passord må inneholde minst én stor bokstav", "specialChar": "Passord må inneholde minst ett spesialtegn", "confirmLabel": "Bekreft passord", diff --git a/src/env.ts b/src/env.ts index 2748fd9d..f32b5608 100644 --- a/src/env.ts +++ b/src/env.ts @@ -24,6 +24,7 @@ export const env = createEnv({ FEIDE_TOKEN_ENDPOINT: z.string(), FEIDE_USERINFO_ENDPOINT: z.string(), FEIDE_EXTENDED_USERINFO_ENDPOINT: z.string(), + MATRIX_SERVER_NAME: z.string(), MATRIX_SECRET: z.string(), MATRIX_ENDPOINT: z.string(), }, @@ -60,6 +61,7 @@ export const env = createEnv({ FEIDE_USERINFO_ENDPOINT: process.env.FEIDE_USERINFO_ENDPOINT, FEIDE_EXTENDED_USERINFO_ENDPOINT: process.env.FEIDE_EXTENDED_USERINFO_ENDPOINT, + MATRIX_SERVER_NAME: process.env.MATRIX_SERVER_NAME, MATRIX_SECRET: process.env.MATRIX_SECRET, MATRIX_ENDPOINT: process.env.MATRIX_ENDPOINT, NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL, diff --git a/src/server/api/context.ts b/src/server/api/context.ts index 690c683b..197b7b99 100644 --- a/src/server/api/context.ts +++ b/src/server/api/context.ts @@ -14,7 +14,7 @@ type TRPCContext = { db: typeof db; auth: typeof auth; s3: typeof s3; - getTranslations: Translations; + t: Translations; }; async function createContext( @@ -28,7 +28,7 @@ async function createContext( auth, db, s3, - getTranslations: t, + t, }; } diff --git a/src/server/api/errorFormatter.ts b/src/server/api/errorFormatter.ts new file mode 100644 index 00000000..b745d31a --- /dev/null +++ b/src/server/api/errorFormatter.ts @@ -0,0 +1,71 @@ +import type { TRPCContext } from '@/server/api/context'; +import type { TRPCError } from '@trpc/server'; +import type { + Formats, + MessageKeys, + NestedKeyOf, + TranslationValues, +} from 'next-intl'; +import { ZodError } from 'zod'; + +type ToastType = 'info' | 'error' | 'warning' | 'success'; + +type ErrorShape = { + message: string; + code: number; + data: { + code: string; + httpStatus: number; + path?: string; + toast?: ToastType; + [key: string]: unknown; + }; +}; + +function useStringifyTranslations() { + return ( + key: NestedKeyOf, + values?: TranslationValues, + formats?: Formats, + ) => JSON.stringify({ key, values, formats }); +} + +function errorFormatter({ + shape, + error, + ctx, +}: { + shape: ErrorShape; + error: TRPCError; + ctx: TRPCContext | undefined; +}) { + if (error.cause instanceof ZodError) { + const firstError = error.cause.errors[0]; + if (firstError?.message) { + const { key, values, formats } = JSON.parse(firstError.message) as { + key: MessageKeys; + values?: TranslationValues; + formats?: Formats; + }; + const message = ctx?.t(key, values, formats); + return { + ...shape, + message, + data: { + ...shape.data, + toast: 'error', + }, + }; + } + } + + return { + ...shape, + data: { + ...shape.data, + toast: (error.cause as { toast?: ToastType })?.toast, + }, + }; +} + +export { errorFormatter, useStringifyTranslations }; diff --git a/src/server/api/procedures.ts b/src/server/api/procedures.ts index 5d4d08d0..90e098e9 100644 --- a/src/server/api/procedures.ts +++ b/src/server/api/procedures.ts @@ -1,6 +1,5 @@ import { trpc } from '@/server/api/trpc'; import { TRPCError } from '@trpc/server'; -import { getTranslations } from 'next-intl/server'; const timingMiddleware = trpc.middleware(async ({ next, path }) => { const start = Date.now(); @@ -21,16 +20,12 @@ const timingMiddleware = trpc.middleware(async ({ next, path }) => { const publicProcedure = trpc.procedure.use(timingMiddleware); const authMiddleware = trpc.middleware(async ({ next, ctx }) => { - const t = await getTranslations({ - locale: ctx.locale, - namespace: 'api', - }); const { user, session } = await ctx.auth(); if (!session) { throw new TRPCError({ code: 'UNAUTHORIZED', - message: t('notAuthenticated'), + message: ctx.t('api.notAuthenticated'), }); } return next({ diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 709b69f9..baa551a4 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -40,7 +40,7 @@ const authRouter = createRouter({ if (clientIP !== null && !ipBucket.check(clientIP, 1)) { throw new TRPCError({ code: 'TOO_MANY_REQUESTS', - message: 'api.tooManyRequests', + message: ctx.t('api.tooManyRequests'), }); } @@ -65,14 +65,14 @@ const authRouter = createRouter({ }), signIn: publicProcedure .input(accountSignInSchema()) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const headerStore = await headers(); const clientIP = headerStore.get('X-Forwarded-For'); if (clientIP !== null && !ipBucket.check(clientIP, 1)) { throw new TRPCError({ code: 'TOO_MANY_REQUESTS', - message: 'api.tooManyRequests', + message: ctx.t('api.tooManyRequests'), cause: { toast: 'warning' }, }); } @@ -87,7 +87,7 @@ const authRouter = createRouter({ if (!throttler.consume(user.id)) { throw new TRPCError({ code: 'TOO_MANY_REQUESTS', - message: 'api.tooManyRequests', + message: ctx.t('api.tooManyRequests'), cause: { toast: 'warning' }, }); } @@ -99,7 +99,7 @@ const authRouter = createRouter({ if (!validPassword) { throw new TRPCError({ code: 'UNAUTHORIZED', - message: 'auth.invalidCredentials', + message: ctx.t('auth.invalidCredentials'), }); } throttler.reset(user.id); diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 9aa16353..edbdb5a2 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -1,34 +1,11 @@ import type { createContext } from '@/server/api/context'; +import { errorFormatter } from '@/server/api/errorFormatter'; import { initTRPC } from '@trpc/server'; import superjson from 'superjson'; -import { ZodError } from 'zod'; - -type ToastType = 'info' | 'error' | 'warning' | 'success'; const trpc = initTRPC.context().create({ transformer: superjson, - errorFormatter({ shape, error, ctx }) { - if (error.cause instanceof ZodError) { - const firstError = error.cause.errors[0]; - return { - ...shape, - message: ctx?.t(firstError?.message as keyof Messages), - data: { - ...shape.data, - toast: 'error', - }, - }; - } - - return { - ...shape, - message: ctx?.t(shape.message as keyof Messages), - data: { - ...shape.data, - toast: (error.cause as { toast?: ToastType })?.toast, - }, - }; - }, + errorFormatter, }); const createCallerFactory = trpc.createCallerFactory; diff --git a/src/validations/auth/accountSignInSchema.ts b/src/validations/auth/accountSignInSchema.ts index 71f2b2ef..c3734351 100644 --- a/src/validations/auth/accountSignInSchema.ts +++ b/src/validations/auth/accountSignInSchema.ts @@ -1,33 +1,21 @@ import { useTranslations } from 'next-intl'; + +import { useStringifyTranslations } from '@/server/api/errorFormatter'; import { z } from 'zod'; function accountSignInSchema() { - const t = typeof window !== 'undefined' ? useTranslations() : undefined; + const t = + typeof window !== 'undefined' + ? useTranslations() + : useStringifyTranslations(); return z.object({ username: z .string() - .min( - 1, - t ? t('auth.form.username.required') : 'auth.form.username.required', - ) - .min( - 5, - t ? t('auth.form.username.minLength') : 'auth.form.username.minLength', - ) - .max( - 8, - t ? t('auth.form.username.maxLength') : 'auth.form.username.maxLength', - ) - .regex( - /^[a-z]+$/, - t ? t('auth.form.username.invalid') : 'auth.form.username.invalid', - ), - password: z - .string() - .min( - 1, - t ? t('auth.form.password.required') : 'auth.form.password.required', - ), + .min(1, t('auth.form.username.required')) + .min(5, t('auth.form.username.minLength', { count: 5 })) + .max(8, t('auth.form.username.maxLength', { count: 8 })) + .regex(/^[a-z]+$/, t('auth.form.username.invalid')), + password: z.string().min(1, t('auth.form.password.required')), }); } diff --git a/src/validations/auth/accountSignUpSchema.ts b/src/validations/auth/accountSignUpSchema.ts new file mode 100644 index 00000000..83e170ad --- /dev/null +++ b/src/validations/auth/accountSignUpSchema.ts @@ -0,0 +1,21 @@ +import { useStringifyTranslations } from '@/server/api/errorFormatter'; +import { useTranslations } from 'next-intl'; +import { z } from 'zod'; + +function accountSignUpSchema() { + const t = + typeof window !== 'undefined' + ? useTranslations() + : useStringifyTranslations(); + return z.object({ + password: z + .string() + .min(1, t('auth.form.password.required')) + .min(8, t('auth.form.password.minLength', { count: 8 })) + .max(50, t('auth.form.password.maxLength', { count: 50 })) + .regex(/[A-Z]/, t('auth.form.password.uppercase')) + .regex(/[^a-zA-Z0-9]/, t('auth.form.password.specialChar')), + }); +} + +export { accountSignUpSchema }; From 466aaeac6d52be289d2aa5898e6bab9eb390731d Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 05:23:19 +0100 Subject: [PATCH 74/93] feat: improve validation --- src/app/api/auth/feide/route.ts | 42 +++++++++++++++++----- src/server/api/routers/auth.ts | 62 +++++++++++++++++++++------------ src/server/auth/email.ts | 14 ++++++++ src/server/auth/feide.ts | 23 ++++++++++++ src/server/auth/phone.ts | 15 ++++++++ src/server/auth/user.ts | 31 +++++------------ src/validations/db.ts | 7 ++++ 7 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 src/server/auth/email.ts create mode 100644 src/server/auth/phone.ts create mode 100644 src/validations/db.ts diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index c5b5115a..137607df 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -1,15 +1,18 @@ import { env } from '@/env'; +import { checkEmailAvailability } from '@/server/auth/email'; import { type ExtendedFeideUserInfo, type FeideUserInfo, validateFeideAuthorization, } from '@/server/auth/feide'; +import { checkPhoneAvailability } from '@/server/auth/phone'; import { createSession, generateSessionToken, setSessionTokenCookie, } from '@/server/auth/session'; import { createUser, getUserFromUsername } from '@/server/auth/user'; +import { insertUserSchema } from '@/validations/db'; import { cookies } from 'next/headers'; import { type NextRequest, NextResponse } from 'next/server'; @@ -78,19 +81,40 @@ export async function GET(request: NextRequest) { const extendedUserInfo: ExtendedFeideUserInfo = await extendedUserInfoResponse.json(); - const date = new Date( + const isPhoneNumberAvailable = await checkPhoneAvailability( + extendedUserInfo.mobile[0], + ); + + if (!isPhoneNumberAvailable) { + return NextResponse.json(null, { status: 400 }); + } + + const isEmailAvailable = await checkEmailAvailability(userInfo.email); + if (!isEmailAvailable) { + return NextResponse.json(null, { status: 400 }); + } + + const birthDate = new Date( `${extendedUserInfo.norEduPersonBirthDate.slice(0, 4)}-${extendedUserInfo.norEduPersonBirthDate.slice(4, 6)}-${extendedUserInfo.norEduPersonBirthDate.slice(6, 8)}`, ); const emailVerifiedAt = userInfo.email_verified ? new Date() : null; - user = await createUser( - username, - extendedUserInfo.givenName[0], - extendedUserInfo.sn[0], - userInfo.email, + + const userValues = { + username: username, + firstName: extendedUserInfo.givenName[0], + lastName: extendedUserInfo.sn[0], + email: userInfo.email, emailVerifiedAt, - date, - extendedUserInfo.mobile[0], - ); + birthDate, + phoneNumber: extendedUserInfo.mobile[0], + }; + + const insertUserSchemaResult = insertUserSchema.safeParse(userValues); + + if (!insertUserSchemaResult.success) { + return NextResponse.json(null, { status: 500 }); + } + user = await createUser(userValues); if (!user) { return NextResponse.json(null, { status: 500 }); diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index baa551a4..507283d2 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -6,8 +6,15 @@ import { import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket'; import { Throttler } from '@/server/api/rate-limit/throttler'; import { createRouter } from '@/server/api/trpc'; -import { createFeideAuthorization } from '@/server/auth/feide'; -import { verifyPasswordHash } from '@/server/auth/password'; +import { + createFeideAuthorization, + setFeideAuthorizationCookies, +} from '@/server/auth/feide'; +import { + hashPassword, + verifyPasswordHash, + verifyPasswordStrength, +} from '@/server/auth/password'; import { deleteSessionTokenCookie, invalidateSession, @@ -17,11 +24,12 @@ import { generateSessionToken, setSessionTokenCookie, } from '@/server/auth/session'; -import { getUserFromUsername } from '@/server/auth/user'; +import { getUserFromUsername, updateUserPassword } from '@/server/auth/user'; import { accountSignInSchema } from '@/validations/auth/accountSignInSchema'; +import { accountSignUpSchema } from '@/validations/auth/accountSignUpSchema'; import { TRPCError } from '@trpc/server'; -import { cookies, headers } from 'next/headers'; +import { headers } from 'next/headers'; import { sanitizeAuth } from '@/server/auth'; @@ -33,7 +41,7 @@ const authRouter = createRouter({ const result = await ctx.auth(); return sanitizeAuth(result); }), - signInFeide: publicProcedure.mutation(async () => { + signInFeide: publicProcedure.mutation(async ({ ctx }) => { const headerStore = await headers(); const clientIP = headerStore.get('X-Forwarded-For'); @@ -43,23 +51,8 @@ const authRouter = createRouter({ message: ctx.t('api.tooManyRequests'), }); } - - const cookieStore = await cookies(); const { state, codeVerifier, url } = await createFeideAuthorization(); - cookieStore.set('feide-state', state, { - path: '/', - httpOnly: true, - sameSite: 'lax', - maxAge: 60 * 10, - secure: env.NODE_ENV === 'production', - }); - cookieStore.set('feide-code-verifier', codeVerifier, { - path: '/', - httpOnly: true, - sameSite: 'lax', - maxAge: 60 * 10, - secure: env.NODE_ENV === 'production', - }); + await setFeideAuthorizationCookies(state, codeVerifier); return url.href; }), @@ -80,7 +73,7 @@ const authRouter = createRouter({ if (!user || !user.passwordHash) { throw new TRPCError({ code: 'UNAUTHORIZED', - message: 'auth.invalidCredentials', + message: ctx.t('auth.invalidCredentials'), }); } @@ -108,6 +101,31 @@ const authRouter = createRouter({ const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); }), + signUp: authenticatedProcedure + .input(accountSignUpSchema()) + .mutation(async ({ input, ctx }) => { + const headerStore = await headers(); + const clientIP = headerStore.get('X-Forwarded-For'); + + if (clientIP !== null && !ipBucket.check(clientIP, 1)) { + throw new TRPCError({ + code: 'TOO_MANY_REQUESTS', + message: ctx.t('api.tooManyRequests'), + cause: { toast: 'warning' }, + }); + } + + const strongPassword = await verifyPasswordStrength(input.password); + if (!strongPassword) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: ctx.t('auth.form.password.weak'), + }); + } + + const hashedPassword = await hashPassword(input.password); + await updateUserPassword(ctx.user.id, hashedPassword); + }), signOut: authenticatedProcedure.mutation(async ({ ctx }) => { await invalidateSession(ctx.session.id); await deleteSessionTokenCookie(); diff --git a/src/server/auth/email.ts b/src/server/auth/email.ts new file mode 100644 index 00000000..e381fd72 --- /dev/null +++ b/src/server/auth/email.ts @@ -0,0 +1,14 @@ +import { db } from '@/server/db'; +import { eq } from 'drizzle-orm'; + +import { users } from '@/server/db/tables'; + +async function checkEmailAvailability(email: string) { + const rows = await db + .select({ email: users.email }) + .from(users) + .where(eq(users.email, email)); + return rows.length === 0; +} + +export { checkEmailAvailability }; diff --git a/src/server/auth/feide.ts b/src/server/auth/feide.ts index 6b5ddedb..e9faffdc 100644 --- a/src/server/auth/feide.ts +++ b/src/server/auth/feide.ts @@ -1,4 +1,5 @@ import { env } from '@/env'; +import { cookies } from 'next/headers'; import { OAuth2Client, OAuth2RequestError, @@ -85,9 +86,31 @@ async function validateFeideAuthorization(code: string, codeVerifier: string) { } } +async function setFeideAuthorizationCookies( + state: string, + codeVerifier: string, +) { + const cookieStore = await cookies(); + cookieStore.set('feide-state', state, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); + cookieStore.set('feide-code-verifier', codeVerifier, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); +} + export { createFeideAuthorization, validateFeideAuthorization, + setFeideAuthorizationCookies, type FeideUserInfo, type ExtendedFeideUserInfo, }; diff --git a/src/server/auth/phone.ts b/src/server/auth/phone.ts new file mode 100644 index 00000000..b40c444a --- /dev/null +++ b/src/server/auth/phone.ts @@ -0,0 +1,15 @@ +import { db } from '@/server/db'; +import { eq } from 'drizzle-orm'; + +import { users } from '@/server/db/tables'; + +async function checkPhoneAvailability(phoneNumber: string) { + const rows = await db + .select({ phoneNumber: users.phoneNumber }) + .from(users) + .where(eq(users.phoneNumber, phoneNumber)); + + return rows.length === 0; +} + +export { checkPhoneAvailability }; diff --git a/src/server/auth/user.ts b/src/server/auth/user.ts index add23500..7e8c63c3 100644 --- a/src/server/auth/user.ts +++ b/src/server/auth/user.ts @@ -1,7 +1,7 @@ import { db } from '@/server/db'; import { eq } from 'drizzle-orm'; -import { users } from '@/server/db/tables'; +import { type InsertUser, users } from '@/server/db/tables'; async function getUserFromUsername(username: string) { return await db.query.users.findFirst({ @@ -9,29 +9,14 @@ async function getUserFromUsername(username: string) { }); } -async function createUser( - username: string, - firstName: string, - lastName: string, - email: string, - emailVerifiedAt: Date | null, - birthDate: Date, - phoneNumber: string, -) { - const [user] = await db - .insert(users) - .values({ - username, - firstName, - lastName, - email, - emailVerifiedAt, - birthDate, - phoneNumber, - }) - .returning(); +async function createUser(userValues: InsertUser) { + const [user] = await db.insert(users).values(userValues).returning(); return user; } -export { getUserFromUsername, createUser }; +async function updateUserPassword(userId: number, passwordHash: string) { + await db.update(users).set({ passwordHash }).where(eq(users.id, userId)); +} + +export { getUserFromUsername, createUser, updateUserPassword }; diff --git a/src/validations/db.ts b/src/validations/db.ts new file mode 100644 index 00000000..43e30cde --- /dev/null +++ b/src/validations/db.ts @@ -0,0 +1,7 @@ +import { createInsertSchema } from 'drizzle-zod'; + +import { users } from '@/server/db/tables'; + +const insertUserSchema = createInsertSchema(users); + +export { insertUserSchema }; From 522f3823b22305155cd655a915c9407955073a24 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 06:51:04 +0100 Subject: [PATCH 75/93] feat: improve feide error handling --- messages/en.json | 9 ++++++ messages/no.json | 9 ++++++ src/app/[locale]/auth/page.tsx | 9 ++++++ src/app/api/auth/feide/route.ts | 44 +++++++++++++++++++++------- src/components/layout/ErrorToast.tsx | 30 +++++++++++++++++++ src/lib/hooks/useMounted.ts | 14 +++++++++ 6 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 src/components/layout/ErrorToast.tsx create mode 100644 src/lib/hooks/useMounted.ts diff --git a/messages/en.json b/messages/en.json index e2be1f47..56748b99 100644 --- a/messages/en.json +++ b/messages/en.json @@ -65,6 +65,15 @@ "mismatch": "Passwords do not match", "weak": "Password is too weak" } + }, + "error": { + "authenticationFailed": "Authentication failed", + "userInfoFailed": "Failed to get user info", + "userInfoMissing": "User info is missing", + "phoneTaken": "Phone number is already taken", + "emailTaken": "Email is already taken", + "invalidUserData": "Invalid user data", + "userCreationFailed": "Failed to create user" } }, "layout": { diff --git a/messages/no.json b/messages/no.json index 6bb11bd4..36cc79c5 100644 --- a/messages/no.json +++ b/messages/no.json @@ -65,6 +65,15 @@ "mismatch": "Passordene stemmer ikke overens", "weak": "Passordet er for svakt" } + }, + "error": { + "authenticationFailed": "Autentisering mislyktes", + "userInfoFailed": "Kunne ikke hente brukerinformasjon", + "userInfoMissing": "Brukerinformasjon mangler", + "phoneTaken": "Telefonnummeret er allerede i bruk", + "emailTaken": "E-postadressen er allerede i bruk", + "invalidUserData": "Ugyldig brukerdata", + "userCreationFailed": "Kunne ikke opprette bruker" } }, "layout": { diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index f46d61e7..0bb6604e 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -1,4 +1,5 @@ import { FeideButton } from '@/components/auth/FeideButton'; +import { ErrorToast } from '@/components/layout/ErrorToast'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; import { Link } from '@/lib/locale/navigation'; @@ -7,12 +8,19 @@ import { getTranslations, setRequestLocale } from 'next-intl/server'; export default async function SignInPage({ params, + searchParams, }: { params: Promise<{ locale: string }>; + searchParams: Promise<{ error?: string }>; }) { const { locale } = await params; + let { error } = await searchParams; setRequestLocale(locale); const t = await getTranslations('auth'); + + // @ts-expect-error: Unknown if error is a valid translation key + error = t.has(`error.${error}`) ? t(`error.${error}`) : undefined; + return (
    @@ -33,6 +41,7 @@ export default async function SignInPage({
    +
    ); } diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index 137607df..6d574753 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -28,16 +28,22 @@ export async function GET(request: NextRequest) { cookieStore.delete('feide-code-verifier'); if (!code || !state || !storedState || !codeVerifier) { - return NextResponse.json(null, { status: 400 }); + return NextResponse.redirect( + new URL('/auth?error=authenticationFailed', request.url), + ); } if (state !== storedState) { - return NextResponse.json(null, { status: 403 }); + return NextResponse.redirect( + new URL('/auth?error=authenticationFailed', request.url), + ); } const tokens = await validateFeideAuthorization(code, codeVerifier); if (!tokens) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=authenticationFailed', request.url), + ); } const userInfoResponse = await fetch(env.FEIDE_USERINFO_ENDPOINT, { @@ -47,19 +53,25 @@ export async function GET(request: NextRequest) { }); if (!userInfoResponse.ok) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=userInfoFailed', request.url), + ); } const userInfo: FeideUserInfo = await userInfoResponse.json(); if (!userInfo) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=userInfoMissing', request.url), + ); } const username = userInfo['https://n.feide.no/claims/eduPersonPrincipalName'].split('@')[0]; if (!username) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=userInfoMissing', request.url), + ); } let user = await getUserFromUsername(username); @@ -75,7 +87,9 @@ export async function GET(request: NextRequest) { ); if (!extendedUserInfoResponse.ok) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=userInfoFailed', request.url), + ); } const extendedUserInfo: ExtendedFeideUserInfo = @@ -86,12 +100,16 @@ export async function GET(request: NextRequest) { ); if (!isPhoneNumberAvailable) { - return NextResponse.json(null, { status: 400 }); + return NextResponse.redirect( + new URL('/auth?error=phoneTaken', request.url), + ); } const isEmailAvailable = await checkEmailAvailability(userInfo.email); if (!isEmailAvailable) { - return NextResponse.json(null, { status: 400 }); + return NextResponse.redirect( + new URL('/auth?error=emailTaken', request.url), + ); } const birthDate = new Date( @@ -112,12 +130,16 @@ export async function GET(request: NextRequest) { const insertUserSchemaResult = insertUserSchema.safeParse(userValues); if (!insertUserSchemaResult.success) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=invalidUserData', request.url), + ); } user = await createUser(userValues); if (!user) { - return NextResponse.json(null, { status: 500 }); + return NextResponse.redirect( + new URL('/auth?error=userCreationFailed', request.url), + ); } const sessionToken = generateSessionToken(); diff --git a/src/components/layout/ErrorToast.tsx b/src/components/layout/ErrorToast.tsx new file mode 100644 index 00000000..86a3d048 --- /dev/null +++ b/src/components/layout/ErrorToast.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { toast } from '@/components/ui/Toaster'; +import { useMounted } from '@/lib/hooks/useMounted'; +import { useRouter } from '@/lib/locale/navigation'; +import { useEffect } from 'react'; + +function ErrorToast({ + error, + cleanPath, +}: { error?: string; cleanPath?: string }) { + const isMounted = useMounted(); + const router = useRouter(); + + useEffect(() => { + if (isMounted) { + if (error) { + toast.error(error, { richColors: true }); + } + if (cleanPath) { + // @ts-expect-error: HACK: should probably get the correct path type instead of string + router.replace(cleanPath); + } + } + }, [error, isMounted, cleanPath, router]); + + return null; +} + +export { ErrorToast }; diff --git a/src/lib/hooks/useMounted.ts b/src/lib/hooks/useMounted.ts new file mode 100644 index 00000000..f3671907 --- /dev/null +++ b/src/lib/hooks/useMounted.ts @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react'; + +function useMounted() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + return () => setMounted(false); + }, []); + + return mounted; +} + +export { useMounted }; From 68551fa98d4aab4dddb3a84ea8264e820f659aee Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:54:30 +0100 Subject: [PATCH 76/93] feat: create password creation page --- messages/en.json | 3 + messages/no.json | 3 + src/app/[locale]/auth/create-account/page.tsx | 7 +- src/app/[locale]/settings/page.tsx | 6 +- src/components/auth/AccountSignUpForm.tsx | 122 ++++++++++++++++++ src/components/layout/ErrorToast.tsx | 3 +- src/components/layout/Header.tsx | 9 +- src/server/api/routers/auth.ts | 11 +- 8 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/components/auth/AccountSignUpForm.tsx diff --git a/messages/en.json b/messages/en.json index 56748b99..f1660200 100644 --- a/messages/en.json +++ b/messages/en.json @@ -46,6 +46,9 @@ "forgotPassword": "Forgot password", "submit": "Submit", "invalidCredentials": "Invalid credentials", + "createPassword": "Create password", + "passwordDescription": "The password will be used for signing in to Matrix Chat or your Hackerspace Account", + "createAccount": "Create Account", "form": { "username": { "label": "Username", diff --git a/messages/no.json b/messages/no.json index 36cc79c5..fcea4d80 100644 --- a/messages/no.json +++ b/messages/no.json @@ -46,6 +46,9 @@ "forgotPassword": "Glemt passord", "submit": "Send", "invalidCredentials": "Ugyldige påloggingsdetaljer", + "createPassword": "Lag passord", + "passwordDescription": "Passordet vil bli brukt for å logge inn på Matrix Chat eller din Hackerspace-konto", + "createAccount": "Opprett konto", "form": { "username": { "label": "Brukernavn", diff --git a/src/app/[locale]/auth/create-account/page.tsx b/src/app/[locale]/auth/create-account/page.tsx index a38c454c..186a2198 100644 --- a/src/app/[locale]/auth/create-account/page.tsx +++ b/src/app/[locale]/auth/create-account/page.tsx @@ -1,3 +1,4 @@ +import { AccountSignUpForm } from '@/components/auth/AccountSignUpForm'; import { setRequestLocale } from 'next-intl/server'; export default async function CreateAccountPage({ @@ -7,9 +8,5 @@ export default async function CreateAccountPage({ }) { const { locale } = await params; setRequestLocale(locale); - return ( -
    - This page is for adding info to account after first login -
    - ); + return ; } diff --git a/src/app/[locale]/settings/page.tsx b/src/app/[locale]/settings/page.tsx index 1e21b52b..68986de3 100644 --- a/src/app/[locale]/settings/page.tsx +++ b/src/app/[locale]/settings/page.tsx @@ -1,15 +1,19 @@ import { api } from '@/lib/api/server'; +import { setRequestLocale } from 'next-intl/server'; export default async function SettingsPage({ params, }: { params: Promise<{ locale: string }>; }) { + const { locale } = await params; + setRequestLocale(locale); + const { user } = await api.auth.state(); return (

    Settings

    -

    {JSON.stringify(user, null, 2)}

    +
    {JSON.stringify(user, null, 2)}
    ); } diff --git a/src/components/auth/AccountSignUpForm.tsx b/src/components/auth/AccountSignUpForm.tsx new file mode 100644 index 00000000..4da37fe5 --- /dev/null +++ b/src/components/auth/AccountSignUpForm.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { accountSignUpSchema } from '@/validations/auth/accountSignUpSchema'; +import { useTranslations } from 'next-intl'; + +import { api } from '@/lib/api/client'; +import { useRouter } from '@/lib/locale/navigation'; + +import { usePending } from '@/components/auth/PendingBar'; +import { PasswordInput } from '@/components/composites/PasswordInput'; +import { Button } from '@/components/ui/Button'; +import { + Form, + FormControl, + FormItem, + FormLabel, + FormMessage, + useForm, +} from '@/components/ui/Form'; +import type { TRPCClientError } from '@/lib/api/types'; + +function AccountSignUpForm() { + const router = useRouter(); + const t = useTranslations('auth'); + const formSchema = accountSignUpSchema(); + const { isPending, setPending } = usePending(); + const signUpMutation = api.auth.signUp.useMutation({ + onMutate: () => setPending(true), + onSettled: () => setPending(false), + }); + + const form = useForm(formSchema, { + validators: { + onChange: formSchema, + onSubmitAsync: async ({ value }) => { + try { + await signUpMutation.mutateAsync(value); + } catch (error: unknown) { + const TRPCError = error as TRPCClientError; + if (!TRPCError.data?.toast) { + return { fields: { password: TRPCError.message } }; + } + return ' '; + } + }, + }, + defaultValues: { + password: '', + confirmPassword: '', + }, + onSubmit: () => { + router.push('/auth/success'); + }, + }); + + return ( +
    +
    +

    {t('createPassword')}

    +

    {t('passwordDescription')}

    +
    +
    + + {(field) => ( + + {t('form.password.label')} + + field.handleChange(e.target.value)} + onBlur={field.handleBlur} + /> + + + + )} + + { + if (value !== fieldApi.form.getFieldValue('password')) { + return t('form.password.mismatch'); + } + return; + }, + }} + > + {(field) => ( + + {t('form.password.confirmLabel')} + + field.handleChange(e.target.value)} + onBlur={field.handleBlur} + /> + + + + )} + +
    + [state.canSubmit]}> + {([canSubmit]) => ( + + )} + +
    +
    +
    + ); +} + +export { AccountSignUpForm }; diff --git a/src/components/layout/ErrorToast.tsx b/src/components/layout/ErrorToast.tsx index 86a3d048..6c98afb6 100644 --- a/src/components/layout/ErrorToast.tsx +++ b/src/components/layout/ErrorToast.tsx @@ -12,6 +12,7 @@ function ErrorToast({ const isMounted = useMounted(); const router = useRouter(); + // biome-ignore lint/correctness/useExhaustiveDependencies: should only show error message once on first render useEffect(() => { if (isMounted) { if (error) { @@ -22,7 +23,7 @@ function ErrorToast({ router.replace(cleanPath); } } - }, [error, isMounted, cleanPath, router]); + }, [isMounted]); return null; } diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index d536cbfb..9cf355db 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -5,11 +5,18 @@ import { MobileSheet } from '@/components/layout/header/MobileSheet'; import { Nav } from '@/components/layout/header/Nav'; import { ProfileMenu } from '@/components/layout/header/ProfileMenu'; import { api } from '@/lib/api/server'; -import { getTranslations } from 'next-intl/server'; +import { redirect } from '@/lib/locale/navigation'; +import { getLocale, getTranslations } from 'next-intl/server'; async function Header() { + const locale = await getLocale(); const t = await getTranslations('layout'); const { user } = await api.auth.state(); + + if (user && !user.isAccountComplete) { + redirect({ href: '/auth/create-account', locale }); + } + return (
    diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 507283d2..53bd2d33 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -39,7 +39,16 @@ const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 180, 300]); const authRouter = createRouter({ state: publicProcedure.query(async ({ ctx }) => { const result = await ctx.auth(); - return sanitizeAuth(result); + const sanitized = sanitizeAuth(result); + return { + user: sanitized.user + ? { + ...sanitized.user, + isAccountComplete: result.user?.passwordHash !== null, + } + : null, + session: sanitized.session, + }; }), signInFeide: publicProcedure.mutation(async ({ ctx }) => { const headerStore = await headers(); From 3afd3ab93ada6f55cb119be3290fca5c8cfc3c96 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:09:10 +0100 Subject: [PATCH 77/93] refactor: translation utilities into usevalidationtranslations --- src/server/api/errorFormatter.ts | 17 ++-------------- src/server/api/locale.ts | 22 ++++++++++++++++++++- src/validations/auth/accountSignInSchema.ts | 8 ++------ src/validations/auth/accountSignUpSchema.ts | 8 ++------ 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/server/api/errorFormatter.ts b/src/server/api/errorFormatter.ts index b745d31a..98025bab 100644 --- a/src/server/api/errorFormatter.ts +++ b/src/server/api/errorFormatter.ts @@ -1,11 +1,6 @@ import type { TRPCContext } from '@/server/api/context'; import type { TRPCError } from '@trpc/server'; -import type { - Formats, - MessageKeys, - NestedKeyOf, - TranslationValues, -} from 'next-intl'; +import type { Formats, MessageKeys, TranslationValues } from 'next-intl'; import { ZodError } from 'zod'; type ToastType = 'info' | 'error' | 'warning' | 'success'; @@ -22,14 +17,6 @@ type ErrorShape = { }; }; -function useStringifyTranslations() { - return ( - key: NestedKeyOf, - values?: TranslationValues, - formats?: Formats, - ) => JSON.stringify({ key, values, formats }); -} - function errorFormatter({ shape, error, @@ -68,4 +55,4 @@ function errorFormatter({ }; } -export { errorFormatter, useStringifyTranslations }; +export { errorFormatter }; diff --git a/src/server/api/locale.ts b/src/server/api/locale.ts index b3065b2b..a6c078ae 100644 --- a/src/server/api/locale.ts +++ b/src/server/api/locale.ts @@ -1,4 +1,10 @@ import { routing } from '@/lib/locale'; +import { + type Formats, + type NestedKeyOf, + type TranslationValues, + useTranslations, +} from 'next-intl'; function getLocaleFromRequest(request: Request) { const acceptLanguage = request.headers.get('accept-language'); @@ -16,4 +22,18 @@ function getLocaleFromRequest(request: Request) { : routing.defaultLocale; } -export { getLocaleFromRequest }; +function useStringifyTranslations() { + return ( + key: NestedKeyOf, + values?: TranslationValues, + formats?: Formats, + ) => JSON.stringify({ key, values, formats }); +} + +function useValidationTranslations() { + return typeof window !== 'undefined' + ? useTranslations() + : useStringifyTranslations(); +} + +export { getLocaleFromRequest, useValidationTranslations }; diff --git a/src/validations/auth/accountSignInSchema.ts b/src/validations/auth/accountSignInSchema.ts index c3734351..989d1ecb 100644 --- a/src/validations/auth/accountSignInSchema.ts +++ b/src/validations/auth/accountSignInSchema.ts @@ -1,13 +1,9 @@ -import { useTranslations } from 'next-intl'; +import { useValidationTranslations } from '@/server/api/locale'; -import { useStringifyTranslations } from '@/server/api/errorFormatter'; import { z } from 'zod'; function accountSignInSchema() { - const t = - typeof window !== 'undefined' - ? useTranslations() - : useStringifyTranslations(); + const t = useValidationTranslations(); return z.object({ username: z .string() diff --git a/src/validations/auth/accountSignUpSchema.ts b/src/validations/auth/accountSignUpSchema.ts index 83e170ad..d83c1252 100644 --- a/src/validations/auth/accountSignUpSchema.ts +++ b/src/validations/auth/accountSignUpSchema.ts @@ -1,12 +1,8 @@ -import { useStringifyTranslations } from '@/server/api/errorFormatter'; -import { useTranslations } from 'next-intl'; +import { useValidationTranslations } from '@/server/api/locale'; import { z } from 'zod'; function accountSignUpSchema() { - const t = - typeof window !== 'undefined' - ? useTranslations() - : useStringifyTranslations(); + const t = useValidationTranslations(); return z.object({ password: z .string() From c4b5f178b3a4107f524902e9df715e13b41c1a7a Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:57:10 +0100 Subject: [PATCH 78/93] feat: create redirects and finish success page --- messages/en.json | 1 + messages/no.json | 1 + src/app/[locale]/auth/account/page.tsx | 12 +++++++++ src/app/[locale]/auth/create-account/page.tsx | 13 ++++++++++ src/app/[locale]/auth/layout.tsx | 12 ++++----- src/app/[locale]/auth/page.tsx | 11 ++++++++ src/app/[locale]/auth/success/page.tsx | 25 +++++++++++++------ 7 files changed, 61 insertions(+), 14 deletions(-) diff --git a/messages/en.json b/messages/en.json index f1660200..ac628edd 100644 --- a/messages/en.json +++ b/messages/en.json @@ -40,6 +40,7 @@ "signInWith": "Sign in with", "hackerspaceAccount": "Hackerspace Account", "success": "Success!", + "successDescription": "Congratulations, you are now a member of Hackerspace NTNU!", "home": "Home", "signIn": "Sign in", "useYourAccount": "Use your account", diff --git a/messages/no.json b/messages/no.json index fcea4d80..1393e5e4 100644 --- a/messages/no.json +++ b/messages/no.json @@ -40,6 +40,7 @@ "signInWith": "Logg inn med", "hackerspaceAccount": "Hackerspace-konto", "success": "Suksess!", + "successDescription": "Gratulerer, du er nå medlem av Hackerspace NTNU!", "home": "Hjem", "signIn": "Logg inn", "useYourAccount": "Bruk din konto", diff --git a/src/app/[locale]/auth/account/page.tsx b/src/app/[locale]/auth/account/page.tsx index 30725ec8..3c12d86c 100644 --- a/src/app/[locale]/auth/account/page.tsx +++ b/src/app/[locale]/auth/account/page.tsx @@ -1,4 +1,6 @@ import { AccountSignInForm } from '@/components/auth/AccountSignInForm'; +import { api } from '@/lib/api/server'; +import { redirect } from '@/lib/locale/navigation'; import { setRequestLocale } from 'next-intl/server'; export default async function AccountPage({ @@ -8,5 +10,15 @@ export default async function AccountPage({ }) { const { locale } = await params; setRequestLocale(locale); + + const { user } = await api.auth.state(); + + if (user) { + if (!user.isAccountComplete) { + redirect({ href: '/auth/create-account', locale }); + } + redirect({ href: '/', locale }); + } + return ; } diff --git a/src/app/[locale]/auth/create-account/page.tsx b/src/app/[locale]/auth/create-account/page.tsx index 186a2198..2b880766 100644 --- a/src/app/[locale]/auth/create-account/page.tsx +++ b/src/app/[locale]/auth/create-account/page.tsx @@ -1,4 +1,6 @@ import { AccountSignUpForm } from '@/components/auth/AccountSignUpForm'; +import { api } from '@/lib/api/server'; +import { redirect } from '@/lib/locale/navigation'; import { setRequestLocale } from 'next-intl/server'; export default async function CreateAccountPage({ @@ -8,5 +10,16 @@ export default async function CreateAccountPage({ }) { const { locale } = await params; setRequestLocale(locale); + + const { user } = await api.auth.state(); + + if (user) { + if (user.isAccountComplete) { + redirect({ href: '/', locale }); + } + } else { + redirect({ href: '/auth', locale }); + } + return ; } diff --git a/src/app/[locale]/auth/layout.tsx b/src/app/[locale]/auth/layout.tsx index f210c37d..b54e9025 100644 --- a/src/app/[locale]/auth/layout.tsx +++ b/src/app/[locale]/auth/layout.tsx @@ -1,13 +1,12 @@ -import { NextIntlClientProvider } from 'next-intl'; -import { getMessages } from 'next-intl/server'; -import { setRequestLocale } from 'next-intl/server'; -import { getTranslations } from 'next-intl/server'; - import { PendingBar, PendingProvider } from '@/components/auth/PendingBar'; import { LogoLink } from '@/components/layout/LogoLink'; import { Main } from '@/components/layout/Main'; import { AnimatePresenceProvider } from '@/components/providers/AnimatePresenceProvider'; import { Card, CardHeader } from '@/components/ui/Card'; +import { NextIntlClientProvider } from 'next-intl'; +import { getMessages } from 'next-intl/server'; +import { setRequestLocale } from 'next-intl/server'; +import { getTranslations } from 'next-intl/server'; type AuthLayoutProps = { children: React.ReactNode; @@ -34,9 +33,10 @@ export default async function AuthLayout({ const { locale } = await params; setRequestLocale(locale); const { auth, ui } = await getMessages(); + return (
    - + diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index 0bb6604e..f01f1e27 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -2,7 +2,9 @@ import { FeideButton } from '@/components/auth/FeideButton'; import { ErrorToast } from '@/components/layout/ErrorToast'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; +import { api } from '@/lib/api/server'; import { Link } from '@/lib/locale/navigation'; +import { redirect } from '@/lib/locale/navigation'; import { FingerprintIcon } from 'lucide-react'; import { getTranslations, setRequestLocale } from 'next-intl/server'; @@ -18,6 +20,15 @@ export default async function SignInPage({ setRequestLocale(locale); const t = await getTranslations('auth'); + const { user } = await api.auth.state(); + + if (user) { + if (!user.isAccountComplete) { + redirect({ href: '/auth/create-account', locale }); + } + redirect({ href: '/', locale }); + } + // @ts-expect-error: Unknown if error is a valid translation key error = t.has(`error.${error}`) ? t(`error.${error}`) : undefined; diff --git a/src/app/[locale]/auth/success/page.tsx b/src/app/[locale]/auth/success/page.tsx index 09e20f22..a854a8ce 100644 --- a/src/app/[locale]/auth/success/page.tsx +++ b/src/app/[locale]/auth/success/page.tsx @@ -1,7 +1,9 @@ import { SuccessParticles } from '@/components/auth/SuccessParticles'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; +import { api } from '@/lib/api/server'; import { Link } from '@/lib/locale/navigation'; +import { redirect } from '@/lib/locale/navigation'; import { getTranslations, setRequestLocale } from 'next-intl/server'; export default async function SuccessPage({ @@ -12,19 +14,26 @@ export default async function SuccessPage({ const { locale } = await params; setRequestLocale(locale); const t = await getTranslations('auth'); + + const { user } = await api.auth.state(); + + if (user) { + if (!user.isAccountComplete) { + redirect({ href: '/auth/create-account', locale }); + } + } else { + redirect({ href: '/auth', locale }); + } + return ( -
    +

    {t('success')}

    -

    - { - 'you are now a member of Hackerspace. Now you can finally start praying to our one true leader' - } -

    +

    {t('successDescription')}

    -
    -
    From ad66b73be67c0f770d38698d036067f510fb91fa Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:25:01 +0100 Subject: [PATCH 79/93] feat: write documentation for getting docker running and package.json scripts --- CONTRIBUTING.md | 152 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e151d23..69ba6935 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,15 +6,161 @@ Make sure you have Bun installed on your machine. If you don't have it, you can download it [here](https://bun.sh/docs/installation). Here is some information about Bun in Next.js: [Build an app with Next.js and Bun](https://bun.sh/guides/ecosystem/nextjs). +#### Dependencies + First, install dependencies: ```bash bun install ``` -Also, setup environment variables by copying the `.env.example` file to `.env` and fill in the values. `.env` files are used to store sensitive information like API keys and database credentials and it will not be committed to the repository. +> [!NOTE] +> When you merge with the `dev` branch you may need to rerun `bun install` in case there are new dependencies added by other contributors. + +#### Environment variables + +Then you need to setup environment variables by creating a `.env` file in the root of the project. +Fill the `.env` file with all variables from the `.env.example` file. You can supply an empty string as the value for variables that you do not need to set for the things you are using. +For example, if you are not working on Feide, you do not need to set the FEIDE_CLIENT_ID, FEIDE_CLIENT_SECRET, etc... + +When finished setting the file up it should be on this form: + +```bash +NODE_ENV="development" +NODE_OPTIONS="--max_old_space_size=16384" +NEXT_TELEMETRY_DISABLED="true" +NEXT_PUBLIC_SITE_URL="http://localhost:3000" +DB_HOST="localhost" +DB_PORT="5432" +DB_USER="user" +# and so on... +``` + +If you have not defined an environment variable you will get an error on this form when you start the dev server: + +```bash +❌ Invalid environment variables: { DB_PORT: [ 'Required' ] } +``` + +Here I had forgotten to set the DB_PORT variable. + +> [!NOTE] +> When you merge with the `dev` branch you may need to update the `.env` file in case there are new environment variables added or modified by other contributors. + +#### Docker + +Next we are gonna setup the database and storage server to run locally. To achieve this we need to install Docker. + +- To install Docker on Linux you can follow the guide [here](https://docs.docker.com/engine/install/) based on your distribution. +- For windows you can install Docker Desktop from [here](https://docs.docker.com/desktop/setup/install/windows-install/). I do not recommend using Docker Desktop as it is very heavy and slow to run + it has a non-commercial license. If you are using windows and know of a better solution please update this guide. +- For MacOS you can use [Colima](https://github.com/abiosoft/colima) as a Docker runtime. Just install it using [homebrew](https://brew.sh). You can also use Docker Desktop [here](https://docs.docker.com/desktop/setup/install/mac-install/) if you prefer, but that is not recommended. + + ```bash + # MacOS only + brew install colima docker + # Start colima + colima start + ``` + +Then we need to install Docker Compose. + +- For Linux follow the plugin guide for your distro [here](https://docs.docker.com/compose/install/linux/). +- For Windows I have no idea, please update this guide. +- For MacOS you can install Docker Compose using homebrew. + + ```bash + # MacOS only + brew install docker-compose + ``` + +#### Database and storage server + +When you have installed Docker and Docker Compose you can start the database and storage server by running: + +```bash +bun run dev:setup +``` + +Now this command is a fancy all in one command that restarts and resets both the database and storage server pushes any new migrations and seeds the database. If you prefer to use the individual commands they will be outlined below. + +You can stop both with: + +```bash +bun run dev:stop +``` + +> [!TIP] +> In the future we may add a way to SSH tunnel and use the database and storage server for the staging solution locally. This is only beneficial if you are only working on the frontend and do not need to modify the database. + +Now for a more indept guide on all the scripts for the database and storage server. First the basics that they both have in common. Now remember that you in theory only need the fancy command above if you are not doing any debugging. It just runs these other scripts for you under the hood. + +To start the database and storage server you can run: + +```bash +bun run db:start +# or +bun run s3:start +``` + +To stop the database and storage server you can run: + +```bash +bun run db:stop +# or +bun run s3:stop +``` + +To delete all the data for the database and storage server you can run: + +```bash +bun run db:delete +# or +bun run s3:delete +``` + +To print the logs continuely from the database and storage server you can run: + +```bash +bun run db:logs +# or +bun run s3:logs +``` + +For migrations we have multiple commands to help us. First to quickly create migrations based on a change you have made to the tables (and not store it) and apply it to the running database you can run: + +```bash +bun run db:push +``` + +This is the command `dev:setup` uses under the hood to push the migrations to the database. + +If you have finished making changes to the database and want to create and store the migration you can run: + +```bash +bun run db:generate +``` + +This will generate new migrations into the `@/server/db/migrations` directory. + +If you want to run the generated migrations on your database you can run: + +```bash +bun run db:migrate +``` + +Lastly we have a very useful command that starts an interactive web server where you can look at the database and run queries on it. This is very useful for debugging and testing. You can use it with: + +```bash +bun run db:studio +``` + +This lets you access the database locally on: + +If you want to access the storage server locally in a web browser you can always access it on: when it is running. The password and username is what you have defined in the `.env` file. + +#### Development server -Then, run the development server: +Then you can run the development server and see the website: ```bash bun dev --turbo @@ -55,7 +201,7 @@ We are using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0. - To keep the code as consistent as possible use functions for react components or hooks instead of const variables with arrow function syntax. An exception is when using the forwardRef hook or when creating compound components. - Only use default export for pages or layouts etc. since it is required by Next.js. For everything else use named exports. This is to make it easier to find the components in the codebase or change them without ending up with different names for the same component. - Use `type` instead of `interface` for typescript types. This is to keep the code consistent and to make it easier to read. Also `type` is more flexible than `interface` since it can be used for unions and intersections. -- When using custom icons that are not provided by lucide, make sure to add them as React Components (SVGs) to the `components/assets/icons` folder. This improves performance since the icons are handled as vectors and not as images. +- When using custom icons that are not provided by lucide, make sure to add them as React Components (SVGs) to the `components/assets/icons` directory. This improves performance since the icons are handled as vectors and not as images. - For internationalization use the `useTranslations` and `getTranslations` (for async components) hooks from `next-intl`. - For client components pass the translations as props using the `t` prop for consistency. - On the backend when returning error messages, you can just return the key to the translation as the error message and it will be formatted correctly. From 1420d45225531c88b9f0e3f0bcfb935ea49d8dac Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:48:08 +0100 Subject: [PATCH 80/93] feat: update migrations, indexes and relations and add email verification requests table --- CONTRIBUTING.md | 6 + src/lib/constants.ts | 11 ++ ...ayla_miller.sql => 0000_eminent_kylun.sql} | 21 +- .../db/migrations/meta/0000_snapshot.json | 186 +++++++++++++++++- src/server/db/migrations/meta/_journal.json | 4 +- src/server/db/seed.ts | 27 +-- src/server/db/tables/emails.ts | 56 ++++++ src/server/db/tables/index.ts | 1 + src/server/db/tables/skills.ts | 15 +- src/server/db/tables/users.ts | 83 ++++---- 10 files changed, 341 insertions(+), 69 deletions(-) create mode 100644 src/lib/constants.ts rename src/server/db/migrations/{0000_curious_layla_miller.sql => 0000_eminent_kylun.sql} (55%) create mode 100644 src/server/db/tables/emails.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69ba6935..63d819de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,6 +148,12 @@ If you want to run the generated migrations on your database you can run: bun run db:migrate ``` +We also have a command that seeds the database (fills it up with mock data). This command runs the `@/server/db/seed.ts` file, which needs to be updated when we need data to be available in the database. You can run it with: + +```bash +bun run db:seed +``` + Lastly we have a very useful command that starts an interactive web server where you can look at the database and run queries on it. This is very useful for debugging and testing. You can use it with: ```bash diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 00000000..464a793b --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,11 @@ +const skillIdentifiers = [ + 'printing', + 'souldering', + 'raspberry', + 'unix', + 'laser', + 'workshop', + 'microcontroller', +] as const; + +export { skillIdentifiers }; diff --git a/src/server/db/migrations/0000_curious_layla_miller.sql b/src/server/db/migrations/0000_eminent_kylun.sql similarity index 55% rename from src/server/db/migrations/0000_curious_layla_miller.sql rename to src/server/db/migrations/0000_eminent_kylun.sql index a9b5103f..d417dafb 100644 --- a/src/server/db/migrations/0000_curious_layla_miller.sql +++ b/src/server/db/migrations/0000_eminent_kylun.sql @@ -1,4 +1,13 @@ CREATE TYPE "public"."locale" AS ENUM('en', 'no');--> statement-breakpoint +CREATE TYPE "public"."skill_identifiers" AS ENUM('printing', 'souldering', 'raspberry', 'unix', 'laser', 'workshop', 'microcontroller');--> statement-breakpoint +CREATE TABLE "email_verification_requests" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" integer NOT NULL, + "code" text NOT NULL, + "email" varchar(254) NOT NULL, + "expires_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint CREATE TABLE "coffee" ( "id" serial PRIMARY KEY NOT NULL, "created_at" timestamp DEFAULT now() NOT NULL @@ -28,7 +37,7 @@ CREATE TABLE "users" ( --> statement-breakpoint CREATE TABLE "skills" ( "id" serial PRIMARY KEY NOT NULL, - "identifier" varchar(256) NOT NULL, + "identifier" "skill_identifiers" NOT NULL, CONSTRAINT "skills_identifier_unique" UNIQUE("identifier") ); --> statement-breakpoint @@ -38,6 +47,14 @@ CREATE TABLE "users_skills" ( CONSTRAINT "users_skills_user_id_skill_id_pk" PRIMARY KEY("user_id","skill_id") ); --> statement-breakpoint +ALTER TABLE "email_verification_requests" ADD CONSTRAINT "email_verification_requests_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "users_skills" ADD CONSTRAINT "users_skills_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "users_skills" ADD CONSTRAINT "users_skills_skill_id_skills_id_fk" FOREIGN KEY ("skill_id") REFERENCES "public"."skills"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file +ALTER TABLE "users_skills" ADD CONSTRAINT "users_skills_skill_id_skills_id_fk" FOREIGN KEY ("skill_id") REFERENCES "public"."skills"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "email_verification_user_id_idx" ON "email_verification_requests" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "sessions_user_id_idx" ON "session" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint +CREATE INDEX "users_username_idx" ON "users" USING btree ("username");--> statement-breakpoint +CREATE INDEX "users_phone_number_idx" ON "users" USING btree ("phone_number");--> statement-breakpoint +CREATE INDEX "users_skills_user_id_idx" ON "users_skills" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "users_skills_skill_id_idx" ON "users_skills" USING btree ("skill_id"); \ No newline at end of file diff --git a/src/server/db/migrations/meta/0000_snapshot.json b/src/server/db/migrations/meta/0000_snapshot.json index 7074ffd4..57496b40 100644 --- a/src/server/db/migrations/meta/0000_snapshot.json +++ b/src/server/db/migrations/meta/0000_snapshot.json @@ -1,9 +1,78 @@ { - "id": "89877f9b-d38c-413f-a313-a6f6358abe9b", + "id": "608a60fa-7d26-48be-a385-e5b175a7acf8", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", "tables": { + "public.email_verification_requests": { + "name": "email_verification_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(254)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_verification_user_id_idx": { + "name": "email_verification_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_verification_requests_user_id_users_id_fk": { + "name": "email_verification_requests_user_id_users_id_fk", + "tableFrom": "email_verification_requests", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.coffee": { "name": "coffee", "schema": "", @@ -53,7 +122,23 @@ "notNull": true } }, - "indexes": {}, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, "foreignKeys": { "session_user_id_users_id_fk": { "name": "session_user_id_users_id_fk", @@ -137,7 +222,53 @@ "notNull": false } }, - "indexes": {}, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_phone_number_idx": { + "name": "users_phone_number_idx", + "columns": [ + { + "expression": "phone_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": { @@ -173,7 +304,8 @@ }, "identifier": { "name": "identifier", - "type": "varchar(256)", + "type": "skill_identifiers", + "typeSchema": "public", "primaryKey": false, "notNull": true } @@ -209,7 +341,38 @@ "notNull": true } }, - "indexes": {}, + "indexes": { + "users_skills_user_id_idx": { + "name": "users_skills_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_skills_skill_id_idx": { + "name": "users_skills_skill_id_idx", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, "foreignKeys": { "users_skills_user_id_users_id_fk": { "name": "users_skills_user_id_users_id_fk", @@ -247,6 +410,19 @@ "name": "locale", "schema": "public", "values": ["en", "no"] + }, + "public.skill_identifiers": { + "name": "skill_identifiers", + "schema": "public", + "values": [ + "printing", + "souldering", + "raspberry", + "unix", + "laser", + "workshop", + "microcontroller" + ] } }, "schemas": {}, diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json index 797f86ca..30ed21dd 100644 --- a/src/server/db/migrations/meta/_journal.json +++ b/src/server/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1736910908942, - "tag": "0000_curious_layla_miller", + "when": 1737024424329, + "tag": "0000_eminent_kylun", "breakpoints": true } ] diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts index 0036132d..c64943de 100644 --- a/src/server/db/seed.ts +++ b/src/server/db/seed.ts @@ -1,3 +1,4 @@ +import { skillIdentifiers } from '@/lib/constants'; import { type InsertSkill, type InsertUser, @@ -40,29 +41,9 @@ async function main() { console.log('User inserted'); console.log('Inserting skills...'); - const skillsdata: InsertSkill[] = [ - { - identifier: 'printing', - }, - { - identifier: 'unix', - }, - { - identifier: 'raspberry', - }, - { - identifier: 'laser', - }, - { - identifier: 'arduino', - }, - { - identifier: 'souldering', - }, - { - identifier: 'workshop', - }, - ]; + const skillsdata: InsertSkill[] = skillIdentifiers.map((identifier) => ({ + identifier, + })); const insertedSkills = await db.insert(skills).values(skillsdata).returning(); console.log('Skills inserted'); diff --git a/src/server/db/tables/emails.ts b/src/server/db/tables/emails.ts new file mode 100644 index 00000000..c5373b44 --- /dev/null +++ b/src/server/db/tables/emails.ts @@ -0,0 +1,56 @@ +import { + type InferInsertModel, + type InferSelectModel, + relations, +} from 'drizzle-orm'; +import { + index, + integer, + pgTable, + text, + timestamp, + varchar, +} from 'drizzle-orm/pg-core'; + +import { users } from '@/server/db/tables'; + +const emailVerificationRequests = pgTable( + 'email_verification_requests', + { + id: text('id').primaryKey(), + userId: integer('user_id') + .notNull() + .references(() => users.id), + code: text('code').notNull(), + email: varchar('email', { length: 254 }).notNull(), + expiresAt: timestamp('expires_at', { + withTimezone: true, + mode: 'date', + }).notNull(), + }, + (table) => [index('email_verification_user_id_idx').on(table.userId)], +); + +const emailVerificationRequestsRelations = relations( + emailVerificationRequests, + ({ one }) => ({ + user: one(users, { + fields: [emailVerificationRequests.userId], + references: [users.id], + }), + }), +); + +type SelectEmailVerificationRequest = InferSelectModel< + typeof emailVerificationRequests +>; +type InsertEmailVerificationRequest = InferInsertModel< + typeof emailVerificationRequests +>; + +export { + emailVerificationRequests, + emailVerificationRequestsRelations, + type SelectEmailVerificationRequest, + type InsertEmailVerificationRequest, +}; diff --git a/src/server/db/tables/index.ts b/src/server/db/tables/index.ts index 06e02ad7..0950d808 100644 --- a/src/server/db/tables/index.ts +++ b/src/server/db/tables/index.ts @@ -2,3 +2,4 @@ export * from './office'; export * from './users'; export * from './locales'; export * from './skills'; +export * from './emails'; diff --git a/src/server/db/tables/skills.ts b/src/server/db/tables/skills.ts index 606ddd11..6b00c4e6 100644 --- a/src/server/db/tables/skills.ts +++ b/src/server/db/tables/skills.ts @@ -1,17 +1,21 @@ +import { skillIdentifiers } from '@/lib/constants'; import { users } from '@/server/db/tables'; import { relations } from 'drizzle-orm'; import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; import { + index, integer, + pgEnum, pgTable, primaryKey, serial, - varchar, } from 'drizzle-orm/pg-core'; +const skillIdentifiersEnum = pgEnum('skill_identifiers', skillIdentifiers); + const skills = pgTable('skills', { id: serial('id').primaryKey(), - identifier: varchar('identifier', { length: 256 }).unique().notNull(), + identifier: skillIdentifiersEnum('identifier').unique().notNull(), }); const usersSkills = pgTable( @@ -25,7 +29,11 @@ const usersSkills = pgTable( .notNull(), }, (table) => { - return [primaryKey({ columns: [table.userId, table.skillId] })]; + return [ + primaryKey({ columns: [table.userId, table.skillId] }), + index('users_skills_user_id_idx').on(table.userId), + index('users_skills_skill_id_idx').on(table.skillId), + ]; }, ); @@ -50,6 +58,7 @@ type SelectUserSkill = InferSelectModel; type InsertUserSkill = InferInsertModel; export { + skillIdentifiersEnum, skills, skillsRelations, usersSkills, diff --git a/src/server/db/tables/users.ts b/src/server/db/tables/users.ts index 724c8f64..d9338d62 100644 --- a/src/server/db/tables/users.ts +++ b/src/server/db/tables/users.ts @@ -1,7 +1,8 @@ -import { usersSkills } from '@/server/db/tables'; +import { emailVerificationRequests, usersSkills } from '@/server/db/tables'; import { relations } from 'drizzle-orm'; import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; import { + index, integer, pgTable, serial, @@ -10,44 +11,58 @@ import { varchar, } from 'drizzle-orm/pg-core'; -const users = pgTable('users', { - id: serial('id').primaryKey(), - createdAt: timestamp('created_at', { - withTimezone: true, - mode: 'date', - }) - .notNull() - .defaultNow(), - username: varchar('username', { length: 8 }).unique().notNull(), - firstName: varchar('first_name', { length: 30 }).notNull(), - lastName: varchar('last_name', { length: 30 }).notNull(), - email: varchar('email', { length: 254 }).unique().notNull(), - emailVerifiedAt: timestamp('email_verified_at', { - withTimezone: true, - mode: 'date', - }), - birthDate: timestamp('birth_date', { - withTimezone: true, - mode: 'date', - }).notNull(), - phoneNumber: varchar('phone_number', { length: 20 }).unique().notNull(), - passwordHash: text('password_hash'), -}); +const users = pgTable( + 'users', + { + id: serial('id').primaryKey(), + createdAt: timestamp('created_at', { + withTimezone: true, + mode: 'date', + }) + .notNull() + .defaultNow(), + username: varchar('username', { length: 8 }).unique().notNull(), + firstName: varchar('first_name', { length: 30 }).notNull(), + lastName: varchar('last_name', { length: 30 }).notNull(), + email: varchar('email', { length: 254 }).unique().notNull(), + emailVerifiedAt: timestamp('email_verified_at', { + withTimezone: true, + mode: 'date', + }), + birthDate: timestamp('birth_date', { + withTimezone: true, + mode: 'date', + }).notNull(), + phoneNumber: varchar('phone_number', { length: 20 }).unique().notNull(), + passwordHash: text('password_hash'), + }, + (table) => [ + index('users_email_idx').on(table.email), + index('users_username_idx').on(table.username), + index('users_phone_number_idx').on(table.phoneNumber), + ], +); const usersRelations = relations(users, ({ many }) => ({ usersSkills: many(usersSkills), + sessions: many(sessions), + emailVerificationRequests: many(emailVerificationRequests), })); -const sessions = pgTable('session', { - id: text('id').primaryKey(), - userId: integer('user_id') - .references(() => users.id) - .notNull(), - expiresAt: timestamp('expires_at', { - withTimezone: true, - mode: 'date', - }).notNull(), -}); +const sessions = pgTable( + 'session', + { + id: text('id').primaryKey(), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + expiresAt: timestamp('expires_at', { + withTimezone: true, + mode: 'date', + }).notNull(), + }, + (table) => [index('sessions_user_id_idx').on(table.userId)], +); const sessionsRelations = relations(sessions, ({ one }) => ({ user: one(users, { From 83d7ef1ac9f4ffdc485cc86e52d46e5e5b3f38b4 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:20:43 +0100 Subject: [PATCH 81/93] docs: add more info --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63d819de..e30309a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -281,3 +281,7 @@ Here is a list of documentations that will help you contribute to the project: - You can also try restarting the whole editor by pressing `cmd + shift + p` and then type `Developer: Reload Window`. On windows you can use `ctrl` instead of `cmd`. + +## GitHub + +- For branch names, use kebab-case (e.g. about-us-page) From cd98a5013b5fbff3afb14ad03eb042040f8d1948 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:05:56 +0100 Subject: [PATCH 82/93] chore: fix title bug on auth page --- src/app/[locale]/auth/layout.tsx | 9 ++++++++- src/components/layout/LogoLink.tsx | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/auth/layout.tsx b/src/app/[locale]/auth/layout.tsx index b54e9025..664c90a0 100644 --- a/src/app/[locale]/auth/layout.tsx +++ b/src/app/[locale]/auth/layout.tsx @@ -32,6 +32,7 @@ export default async function AuthLayout({ }: AuthLayoutProps) { const { locale } = await params; setRequestLocale(locale); + const t = await getTranslations('layout'); const { auth, ui } = await getMessages(); return ( @@ -40,7 +41,13 @@ export default async function AuthLayout({ - +
    - + Date: Thu, 16 Jan 2025 17:20:37 +0100 Subject: [PATCH 83/93] feat: add matrix registration --- messages/en.json | 1 + messages/no.json | 1 + src/server/api/routers/auth.ts | 16 +++++++++++++++ src/server/auth/matrix.ts | 37 +++++++++------------------------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/messages/en.json b/messages/en.json index bc7364d9..c20a0c52 100644 --- a/messages/en.json +++ b/messages/en.json @@ -47,6 +47,7 @@ "forgotPassword": "Forgot password", "submit": "Submit", "invalidCredentials": "Invalid credentials", + "matrixRegistrationFailed": "Matrix user registration failed", "createPassword": "Create password", "passwordDescription": "The password will be used for signing in to Matrix Chat or your Hackerspace Account", "createAccount": "Create Account", diff --git a/messages/no.json b/messages/no.json index 3125e39d..00465cc5 100644 --- a/messages/no.json +++ b/messages/no.json @@ -47,6 +47,7 @@ "forgotPassword": "Glemt passord", "submit": "Send", "invalidCredentials": "Ugyldige påloggingsdetaljer", + "matrixRegistrationFailed": "Matrix bruker registrering feilet", "createPassword": "Lag passord", "passwordDescription": "Passordet vil bli brukt for å logge inn på Matrix Chat eller din Hackerspace-konto", "createAccount": "Opprett konto", diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 53bd2d33..fea567fc 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -32,6 +32,7 @@ import { TRPCError } from '@trpc/server'; import { headers } from 'next/headers'; import { sanitizeAuth } from '@/server/auth'; +import { registerMatrixUser } from '@/server/auth/matrix'; const ipBucket = new RefillingTokenBucket(5, 60); const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 180, 300]); @@ -132,6 +133,21 @@ const authRouter = createRouter({ }); } + try { + const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; + await registerMatrixUser( + ctx.user.username, + displayname, + input.password, + ); + } catch (error) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: ctx.t('auth.matrixRegistrationFailed'), + cause: { toast: 'error' }, + }); + } + const hashedPassword = await hashPassword(input.password); await updateUserPassword(ctx.user.id, hashedPassword); }), diff --git a/src/server/auth/matrix.ts b/src/server/auth/matrix.ts index f6b6b5ae..6e531122 100644 --- a/src/server/auth/matrix.ts +++ b/src/server/auth/matrix.ts @@ -2,22 +2,18 @@ import { env } from '@/env'; import { hmac } from '@oslojs/crypto/hmac'; import { SHA1 } from '@oslojs/crypto/sha1'; -async function getNonce(): Promise { +async function getNonce(): Promise { const getRequest: RequestInfo = new Request( `${env.MATRIX_ENDPOINT}/v1/register`, { method: 'GET' }, ); - try { - const response = await fetch(getRequest); - if (!response.ok) { - throw new Error(`HTTP Error! Status: ${response.status}`); - } - const data = await response.json(); - return data.nonce; - } catch (error) { - console.error('Failed to fetch nonce: ', error); + const response = await fetch(getRequest); + if (!response.ok) { + throw new Error(`HTTP Error! Status: ${response.status}`); } + const data = await response.json(); + return data.nonce; } function generateMAC( @@ -25,7 +21,7 @@ function generateMAC( username: string, password: string, admin = false, -): string { +) { const encoder = new TextEncoder(); const key = encoder.encode(env.MATRIX_SECRET); @@ -57,7 +53,7 @@ async function registerMatrixUser( displayname: string, password: string, admin = false, -): Promise { +) { const nonce = await getNonce(); const hmac = generateMAC(nonce, username, password); @@ -76,22 +72,9 @@ async function registerMatrixUser( body: JSON.stringify(data), }); - if (response.ok) { - const jsonResponse = await response.json(); - console.log('Managed to send POST request:', jsonResponse); - return true; + if (!response.ok) { + throw new Error(`Matrix registration failed: ${response.status}`); } - let errorDetails = ''; - try { - const errorResponse = await response.json(); - errorDetails = JSON.stringify(errorResponse, null, 2); - } catch { - errorDetails = 'Unable to parse error response'; - } - console.error( - `Failed to create user!\nStatus: ${response.status} ${response.statusText}\nDetails: ${errorDetails}`, - ); - return false; } export { registerMatrixUser }; From ae3f67abbe17604ad2bcaeb357cdb8e9ababdf99 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:30:08 +0100 Subject: [PATCH 84/93] chore: matrix updates --- src/server/auth/matrix.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/server/auth/matrix.ts b/src/server/auth/matrix.ts index 6e531122..a99502b7 100644 --- a/src/server/auth/matrix.ts +++ b/src/server/auth/matrix.ts @@ -2,7 +2,7 @@ import { env } from '@/env'; import { hmac } from '@oslojs/crypto/hmac'; import { SHA1 } from '@oslojs/crypto/sha1'; -async function getNonce(): Promise { +async function getNonce() { const getRequest: RequestInfo = new Request( `${env.MATRIX_ENDPOINT}/v1/register`, { method: 'GET' }, @@ -16,8 +16,8 @@ async function getNonce(): Promise { return data.nonce; } -function generateMAC( - nonce: string | undefined, +function generateHMAC( + nonce: string, username: string, password: string, admin = false, @@ -55,24 +55,25 @@ async function registerMatrixUser( admin = false, ) { const nonce = await getNonce(); - const hmac = generateMAC(nonce, username, password); + const hmac = generateHMAC(nonce, username, password); const data = { - nonce: nonce, - username: username, - displayname: displayname, - password: password, - admin: admin, + nonce, + username, + displayname, + password, + admin, mac: hmac, }; const response = await fetch(`${env.MATRIX_ENDPOINT}/v1/register`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'content-type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) { + console.log(await response.json()); throw new Error(`Matrix registration failed: ${response.status}`); } } From 3c78737f64ed9de0ff020b237ea990f0ec1bd1fa Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:47:23 +0100 Subject: [PATCH 85/93] chore: requested fixes --- src/components/composites/PasswordInput.tsx | 6 +++--- src/lib/constants.ts | 2 +- src/lib/locale/index.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/composites/PasswordInput.tsx b/src/components/composites/PasswordInput.tsx index 432a3ba2..e164f227 100644 --- a/src/components/composites/PasswordInput.tsx +++ b/src/components/composites/PasswordInput.tsx @@ -2,17 +2,17 @@ import { EyeIcon, EyeOffIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; -import * as React from 'react'; import { cx } from '@/lib/utils'; +import { forwardRef, useState } from 'react'; import { Button } from '@/components/ui/Button'; import { Input, type InputProps } from '@/components/ui/Input'; -const PasswordInput = React.forwardRef( +const PasswordInput = forwardRef( ({ className, ...props }, ref) => { const t = useTranslations('ui'); - const [showPassword, setShowPassword] = React.useState(false); + const [showPassword, setShowPassword] = useState(false); const disabled = props.value === '' || props.value === undefined || props.disabled; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 464a793b..e779198b 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,6 +1,6 @@ const skillIdentifiers = [ 'printing', - 'souldering', + 'soldering', 'raspberry', 'unix', 'laser', diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 44ef1ab4..861cd5c9 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -36,7 +36,7 @@ const routing = defineRouting({ }, '/auth/success': { en: '/auth/success', - no: '/autentisering/success', + no: '/autentisering/suksess', }, '/settings': { en: '/settings', From c9dcad8317952c7dae64f35268e4e0d3d0af0396 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:52:45 +0100 Subject: [PATCH 86/93] chore: enable dynamic rendering where fetching auth state --- src/app/[locale]/(default)/layout.tsx | 2 ++ src/app/[locale]/auth/layout.tsx | 2 ++ src/app/[locale]/settings/page.tsx | 2 ++ src/server/api/routers/auth.ts | 6 +----- src/server/auth/matrix.ts | 1 - 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/[locale]/(default)/layout.tsx b/src/app/[locale]/(default)/layout.tsx index d917c9d7..a9669f3e 100644 --- a/src/app/[locale]/(default)/layout.tsx +++ b/src/app/[locale]/(default)/layout.tsx @@ -8,6 +8,8 @@ type DefaultLayoutProps = { params: Promise<{ locale: string }>; }; +export const dynamic = 'force-dynamic'; + export default async function DefaultLayout({ children, params, diff --git a/src/app/[locale]/auth/layout.tsx b/src/app/[locale]/auth/layout.tsx index 664c90a0..c21ca663 100644 --- a/src/app/[locale]/auth/layout.tsx +++ b/src/app/[locale]/auth/layout.tsx @@ -26,6 +26,8 @@ export async function generateMetadata({ }; } +export const dynamic = 'force-dynamic'; + export default async function AuthLayout({ children, params, diff --git a/src/app/[locale]/settings/page.tsx b/src/app/[locale]/settings/page.tsx index 68986de3..a0932799 100644 --- a/src/app/[locale]/settings/page.tsx +++ b/src/app/[locale]/settings/page.tsx @@ -1,6 +1,8 @@ import { api } from '@/lib/api/server'; import { setRequestLocale } from 'next-intl/server'; +export const dynamic = 'force-dynamic'; + export default async function SettingsPage({ params, }: { diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index fea567fc..9e6ebe40 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -135,11 +135,7 @@ const authRouter = createRouter({ try { const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; - await registerMatrixUser( - ctx.user.username, - displayname, - input.password, - ); + await registerMatrixUser('yoyoyoyo', displayname, input.password); } catch (error) { throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', diff --git a/src/server/auth/matrix.ts b/src/server/auth/matrix.ts index a99502b7..492937c4 100644 --- a/src/server/auth/matrix.ts +++ b/src/server/auth/matrix.ts @@ -73,7 +73,6 @@ async function registerMatrixUser( }); if (!response.ok) { - console.log(await response.json()); throw new Error(`Matrix registration failed: ${response.status}`); } } From 06ad9ca1ee6b38f9b41316720b5293113b046ab1 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:45:07 +0100 Subject: [PATCH 87/93] feat: add more error logging --- package.json | 2 +- src/app/api/auth/feide/route.ts | 39 ++++++++++++++++++++++----------- src/server/api/routers/auth.ts | 7 ++++-- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 648eb289..58adf78c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "prebuild": "next telemetry disable", "build": "next build", "postbuild": "mkdir -p .next/standalone/public .next/standalone/.next/static && cp -r public/* .next/standalone/public && cp -r .next/static/* .next/standalone/.next/static", - "start": "drizzle-kit migrate && bun run .next/standalone/server.js", + "start": "bun run .next/standalone/server.js", "db:start": "docker compose -f compose.local.yml up db -d", "db:stop": "docker compose -f compose.local.yml down db", "db:delete": "bun -e \"require('fs').rmSync('data/db', { recursive: true, force: true })\"", diff --git a/src/app/api/auth/feide/route.ts b/src/app/api/auth/feide/route.ts index 6d574753..5bd07bb8 100644 --- a/src/app/api/auth/feide/route.ts +++ b/src/app/api/auth/feide/route.ts @@ -28,21 +28,24 @@ export async function GET(request: NextRequest) { cookieStore.delete('feide-code-verifier'); if (!code || !state || !storedState || !codeVerifier) { + console.error('Feide: Missing code, state, storedState or codeVerifier'); return NextResponse.redirect( - new URL('/auth?error=authenticationFailed', request.url), + new URL('/auth?error=authenticationFailed', env.NEXT_PUBLIC_SITE_URL), ); } if (state !== storedState) { + console.error('Feide: State mismatch'); return NextResponse.redirect( - new URL('/auth?error=authenticationFailed', request.url), + new URL('/auth?error=authenticationFailed', env.NEXT_PUBLIC_SITE_URL), ); } const tokens = await validateFeideAuthorization(code, codeVerifier); if (!tokens) { + console.error('Feide: Failed to validate authorization code'); return NextResponse.redirect( - new URL('/auth?error=authenticationFailed', request.url), + new URL('/auth?error=authenticationFailed', env.NEXT_PUBLIC_SITE_URL), ); } @@ -53,15 +56,17 @@ export async function GET(request: NextRequest) { }); if (!userInfoResponse.ok) { + console.error('Feide: Failed to fetch user info'); return NextResponse.redirect( - new URL('/auth?error=userInfoFailed', request.url), + new URL('/auth?error=userInfoFailed', env.NEXT_PUBLIC_SITE_URL), ); } const userInfo: FeideUserInfo = await userInfoResponse.json(); if (!userInfo) { + console.error('Feide: User info missing'); return NextResponse.redirect( - new URL('/auth?error=userInfoMissing', request.url), + new URL('/auth?error=userInfoMissing', env.NEXT_PUBLIC_SITE_URL), ); } @@ -69,8 +74,9 @@ export async function GET(request: NextRequest) { userInfo['https://n.feide.no/claims/eduPersonPrincipalName'].split('@')[0]; if (!username) { + console.error('Feide: Username missing'); return NextResponse.redirect( - new URL('/auth?error=userInfoMissing', request.url), + new URL('/auth?error=userInfoMissing', env.NEXT_PUBLIC_SITE_URL), ); } @@ -87,8 +93,9 @@ export async function GET(request: NextRequest) { ); if (!extendedUserInfoResponse.ok) { + console.error('Feide: Failed to fetch extended user info'); return NextResponse.redirect( - new URL('/auth?error=userInfoFailed', request.url), + new URL('/auth?error=userInfoFailed', env.NEXT_PUBLIC_SITE_URL), ); } @@ -100,15 +107,17 @@ export async function GET(request: NextRequest) { ); if (!isPhoneNumberAvailable) { + console.error('Feide: Phone number taken'); return NextResponse.redirect( - new URL('/auth?error=phoneTaken', request.url), + new URL('/auth?error=phoneTaken', env.NEXT_PUBLIC_SITE_URL), ); } const isEmailAvailable = await checkEmailAvailability(userInfo.email); if (!isEmailAvailable) { + console.error('Feide: Email taken'); return NextResponse.redirect( - new URL('/auth?error=emailTaken', request.url), + new URL('/auth?error=emailTaken', env.NEXT_PUBLIC_SITE_URL), ); } @@ -130,15 +139,17 @@ export async function GET(request: NextRequest) { const insertUserSchemaResult = insertUserSchema.safeParse(userValues); if (!insertUserSchemaResult.success) { + console.error('Feide: Invalid user data'); return NextResponse.redirect( - new URL('/auth?error=invalidUserData', request.url), + new URL('/auth?error=invalidUserData', env.NEXT_PUBLIC_SITE_URL), ); } user = await createUser(userValues); if (!user) { + console.error('Feide: Failed to create user'); return NextResponse.redirect( - new URL('/auth?error=userCreationFailed', request.url), + new URL('/auth?error=userCreationFailed', env.NEXT_PUBLIC_SITE_URL), ); } @@ -146,12 +157,14 @@ export async function GET(request: NextRequest) { const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); - return NextResponse.redirect(new URL('/auth/create-account', request.url)); + return NextResponse.redirect( + new URL('/auth/create-account', env.NEXT_PUBLIC_SITE_URL), + ); } const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); - return NextResponse.redirect(new URL('/', request.url)); + return NextResponse.redirect(new URL('/', env.NEXT_PUBLIC_SITE_URL)); } diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 9e6ebe40..e20604a4 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,4 +1,3 @@ -import { env } from '@/env'; import { authenticatedProcedure, publicProcedure, @@ -135,7 +134,11 @@ const authRouter = createRouter({ try { const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; - await registerMatrixUser('yoyoyoyo', displayname, input.password); + await registerMatrixUser( + ctx.user.username, + displayname, + input.password, + ); } catch (error) { throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', From 55135702247365987ac1d68dcf0327faecb65cf4 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:04:57 +0100 Subject: [PATCH 88/93] fix: matrix registration bug --- src/components/auth/AccountSignUpForm.tsx | 20 +++----------------- src/server/api/routers/auth.ts | 1 + 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/components/auth/AccountSignUpForm.tsx b/src/components/auth/AccountSignUpForm.tsx index 4da37fe5..5b9f10ae 100644 --- a/src/components/auth/AccountSignUpForm.tsx +++ b/src/components/auth/AccountSignUpForm.tsx @@ -17,7 +17,6 @@ import { FormMessage, useForm, } from '@/components/ui/Form'; -import type { TRPCClientError } from '@/lib/api/types'; function AccountSignUpForm() { const router = useRouter(); @@ -27,29 +26,16 @@ function AccountSignUpForm() { const signUpMutation = api.auth.signUp.useMutation({ onMutate: () => setPending(true), onSettled: () => setPending(false), + onSuccess: () => router.push('/auth/success'), }); const form = useForm(formSchema, { - validators: { - onChange: formSchema, - onSubmitAsync: async ({ value }) => { - try { - await signUpMutation.mutateAsync(value); - } catch (error: unknown) { - const TRPCError = error as TRPCClientError; - if (!TRPCError.data?.toast) { - return { fields: { password: TRPCError.message } }; - } - return ' '; - } - }, - }, defaultValues: { password: '', confirmPassword: '', }, - onSubmit: () => { - router.push('/auth/success'); + onSubmit: ({ value }) => { + signUpMutation.mutate(value); }, }); diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index e20604a4..02cf6ec7 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -129,6 +129,7 @@ const authRouter = createRouter({ throw new TRPCError({ code: 'BAD_REQUEST', message: ctx.t('auth.form.password.weak'), + cause: { toast: 'error' }, }); } From cff83a1390bdb3e722014a1aee83e3a7362ac9de Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:47:24 +0100 Subject: [PATCH 89/93] feat: add matrix logo --- src/components/assets/logos/FeideLogo.tsx | 12 +--- .../assets/logos/HackerspaceLogo.tsx | 5 +- src/components/assets/logos/IDILogo.tsx | 12 +--- src/components/assets/logos/MatrixLogo.tsx | 21 ++++++ src/components/assets/logos/NexusLogo.tsx | 72 +++++++------------ src/components/assets/logos/index.tsx | 2 + src/components/auth/FeideButton.tsx | 7 +- src/components/layout/Footer.tsx | 8 ++- src/components/layout/header/MatrixButton.tsx | 13 ++-- src/components/ui/Spinner.tsx | 4 +- 10 files changed, 77 insertions(+), 79 deletions(-) create mode 100644 src/components/assets/logos/MatrixLogo.tsx diff --git a/src/components/assets/logos/FeideLogo.tsx b/src/components/assets/logos/FeideLogo.tsx index 3a5c96f0..d4288861 100644 --- a/src/components/assets/logos/FeideLogo.tsx +++ b/src/components/assets/logos/FeideLogo.tsx @@ -1,13 +1,6 @@ import { cx } from '@/lib/utils'; -function FeideLogo({ - className, - title, - ...props -}: { - className?: string; - title: string; -}) { +function FeideLogo({ className, ...props }: React.SVGProps) { return (
  • diff --git a/src/components/layout/header/MatrixButton.tsx b/src/components/layout/header/MatrixButton.tsx index b713e6b7..4470e100 100644 --- a/src/components/layout/header/MatrixButton.tsx +++ b/src/components/layout/header/MatrixButton.tsx @@ -1,5 +1,5 @@ +import { MatrixLogo } from '@/components/assets/logos'; import { Button } from '@/components/ui/Button'; -import { MessageSquareMoreIcon } from 'lucide-react'; import Link from 'next/link'; type MatrixButtonProps = { @@ -12,11 +12,12 @@ type MatrixButtonProps = { function MatrixButton({ className, t }: MatrixButtonProps) { return ( ); diff --git a/src/components/ui/Spinner.tsx b/src/components/ui/Spinner.tsx index 056c2913..7df591be 100644 --- a/src/components/ui/Spinner.tsx +++ b/src/components/ui/Spinner.tsx @@ -1,4 +1,4 @@ -import { Loader2Icon } from 'lucide-react'; +import { Loader2Icon, type LucideProps } from 'lucide-react'; import { type VariantProps, cva } from '@/lib/utils'; @@ -22,7 +22,7 @@ function Spinner({ className, size, ...props -}: React.SVGProps & VariantProps) { +}: LucideProps & VariantProps) { return ( ); From 0234eefd2a9348fe08252f7611298ff270c45323 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:49:38 +0100 Subject: [PATCH 90/93] chore: move matrix registration to only happen in production --- src/server/api/routers/auth.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 02cf6ec7..fe7e0feb 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,3 +1,4 @@ +import { env } from '@/env'; import { authenticatedProcedure, publicProcedure, @@ -133,19 +134,21 @@ const authRouter = createRouter({ }); } - try { - const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; - await registerMatrixUser( - ctx.user.username, - displayname, - input.password, - ); - } catch (error) { - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: ctx.t('auth.matrixRegistrationFailed'), - cause: { toast: 'error' }, - }); + if (env.NODE_ENV === 'production') { + try { + const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; + await registerMatrixUser( + ctx.user.username, + displayname, + input.password, + ); + } catch (error) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: ctx.t('auth.matrixRegistrationFailed'), + cause: { toast: 'error' }, + }); + } } const hashedPassword = await hashPassword(input.password); From 9f4c5b45245a936ef157a186566bafda9e914972 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:01:03 +0100 Subject: [PATCH 91/93] chore: make env variables optional and creating warnings for devs when services are unavailable --- .env.example | 8 ++++---- messages/en.json | 1 + messages/no.json | 1 + src/env.ts | 18 +++++++++--------- src/server/api/routers/auth.ts | 23 ++++++++++++++++++++++- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 39cdd288..ec17956a 100644 --- a/.env.example +++ b/.env.example @@ -31,14 +31,14 @@ S3_PASSWORD="password" S3_BUCKETS="images" # Auth -FEIDE_CLIENT_ID=" " -FEIDE_CLIENT_SECRET=" " +FEIDE_CLIENT_ID="" +FEIDE_CLIENT_SECRET="" FEIDE_AUTHORIZATION_ENDPOINT="https://auth.dataporten.no/oauth/authorization" FEIDE_TOKEN_ENDPOINT="https://auth.dataporten.no/oauth/token" FEIDE_USERINFO_ENDPOINT="https://auth.dataporten.no/openid/userinfo" FEIDE_EXTENDED_USERINFO_ENDPOINT="https://api.dataporten.no/userinfo/v1/userinfo" # Matrix -MATRIX_SERVER_NAME=" " +MATRIX_SERVER_NAME="" MATRIX_ENDPOINT="https://matrix.hackerspace-ntnu.no/_synapse/admin" -MATRIX_SECRET=" " +MATRIX_SECRET="" diff --git a/messages/en.json b/messages/en.json index c20a0c52..e50eb064 100644 --- a/messages/en.json +++ b/messages/en.json @@ -51,6 +51,7 @@ "createPassword": "Create password", "passwordDescription": "The password will be used for signing in to Matrix Chat or your Hackerspace Account", "createAccount": "Create Account", + "feideNotConfigured": "Feide is not configured", "form": { "username": { "label": "Username", diff --git a/messages/no.json b/messages/no.json index 00465cc5..8a17bd8e 100644 --- a/messages/no.json +++ b/messages/no.json @@ -51,6 +51,7 @@ "createPassword": "Lag passord", "passwordDescription": "Passordet vil bli brukt for å logge inn på Matrix Chat eller din Hackerspace-konto", "createAccount": "Opprett konto", + "feideNotConfigured": "Feide er ikke konfigurert", "form": { "username": { "label": "Brukernavn", diff --git a/src/env.ts b/src/env.ts index f32b5608..0d168d91 100644 --- a/src/env.ts +++ b/src/env.ts @@ -18,15 +18,15 @@ export const env = createEnv({ S3_USER: z.string(), S3_PASSWORD: z.string(), S3_BUCKETS: z.string(), - FEIDE_CLIENT_ID: z.string(), - FEIDE_CLIENT_SECRET: z.string(), - FEIDE_AUTHORIZATION_ENDPOINT: z.string(), - FEIDE_TOKEN_ENDPOINT: z.string(), - FEIDE_USERINFO_ENDPOINT: z.string(), - FEIDE_EXTENDED_USERINFO_ENDPOINT: z.string(), - MATRIX_SERVER_NAME: z.string(), - MATRIX_SECRET: z.string(), - MATRIX_ENDPOINT: z.string(), + FEIDE_CLIENT_ID: z.string().optional(), + FEIDE_CLIENT_SECRET: z.string().optional(), + FEIDE_AUTHORIZATION_ENDPOINT: z.string().optional(), + FEIDE_TOKEN_ENDPOINT: z.string().optional(), + FEIDE_USERINFO_ENDPOINT: z.string().optional(), + FEIDE_EXTENDED_USERINFO_ENDPOINT: z.string().optional(), + MATRIX_SERVER_NAME: z.string().optional(), + MATRIX_SECRET: z.string().optional(), + MATRIX_ENDPOINT: z.string().optional(), }, /** diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index fe7e0feb..7ce559d3 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -61,6 +61,23 @@ const authRouter = createRouter({ message: ctx.t('api.tooManyRequests'), }); } + + if ( + !( + env.FEIDE_CLIENT_ID && + env.FEIDE_CLIENT_SECRET && + env.FEIDE_AUTHORIZATION_ENDPOINT && + env.FEIDE_TOKEN_ENDPOINT && + env.FEIDE_USERINFO_ENDPOINT && + env.FEIDE_EXTENDED_USERINFO_ENDPOINT + ) + ) { + throw new TRPCError({ + code: 'SERVICE_UNAVAILABLE', + message: ctx.t('auth.feideNotConfigured'), + }); + } + const { state, codeVerifier, url } = await createFeideAuthorization(); await setFeideAuthorizationCookies(state, codeVerifier); @@ -134,7 +151,7 @@ const authRouter = createRouter({ }); } - if (env.NODE_ENV === 'production') { + if (env.MATRIX_SERVER_NAME && env.MATRIX_ENDPOINT && env.MATRIX_SECRET) { try { const displayname = `${ctx.user.firstName} ${ctx.user.lastName}`; await registerMatrixUser( @@ -149,6 +166,10 @@ const authRouter = createRouter({ cause: { toast: 'error' }, }); } + } else { + console.log( + 'Matrix account will not be created since the MATRIX environment variables are not set.', + ); } const hashedPassword = await hashPassword(input.password); From 7da9e96776fef929c699682f1523b7cfbdc1ea6d Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:44:36 +0100 Subject: [PATCH 92/93] build: update build and running migrations --- .github/workflows/deploy-script.yml | 9 +- Dockerfile => dockerfilew | 11 ++- package.json | 3 +- ...kylun.sql => 0000_bitter_adam_destine.sql} | 2 +- .../db/migrations/meta/0000_snapshot.json | 4 +- src/server/db/migrations/meta/_journal.json | 4 +- src/server/s3/buckets.ts | 89 +++++++++++++++++++ 7 files changed, 108 insertions(+), 14 deletions(-) rename Dockerfile => dockerfilew (72%) rename src/server/db/migrations/{0000_eminent_kylun.sql => 0000_bitter_adam_destine.sql} (97%) create mode 100644 src/server/s3/buckets.ts diff --git a/.github/workflows/deploy-script.yml b/.github/workflows/deploy-script.yml index 04ca6cfb..76cb2e71 100644 --- a/.github/workflows/deploy-script.yml +++ b/.github/workflows/deploy-script.yml @@ -38,5 +38,10 @@ jobs: cd ${{ inputs.path }} git checkout ${{ inputs.branch }} git pull - docker compose down - docker compose up --build -d + docker compose build app & + docker compose up db s3 -d + sleep 5 + wait + docker compose run --rm app bun run db:migrate + docker compose run --rm app bun run s3:buckets + docker compose up -d app diff --git a/Dockerfile b/dockerfilew similarity index 72% rename from Dockerfile rename to dockerfilew index b16432fd..427b52b6 100644 --- a/Dockerfile +++ b/dockerfilew @@ -2,12 +2,8 @@ FROM imbios/bun-node:22-slim AS base # Install dependencies only when needed FROM base AS deps - WORKDIR /app - ENV NODE_ENV=production - -# Install dependencies COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile @@ -37,12 +33,15 @@ RUN addgroup --system --gid 1002 nodejs && \ # Set the correct permission for prerender cache RUN mkdir .next && chown nextjs:nodejs .next -# Automatically leverage output traces to reduce image size +# Copy necessary files for migrations and S3 setup COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/src/server/db/migrations ./src/server/db/migrations +COPY --from=builder --chown=nextjs:nodejs /app/drizzle.config.ts ./ +COPY --from=builder --chown=nextjs:nodejs /app/src/server ./src/server +COPY --from=builder --chown=nextjs:nodejs /app/package.json ./ USER nextjs EXPOSE 3000 -# server.js is created by next build from the standalone output CMD ["bun", "run", "server.js"] diff --git a/package.json b/package.json index 58adf78c..9bad820b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "s3:start": "docker compose -f compose.local.yml up s3 -d", "s3:stop": "docker compose -f compose.local.yml down s3", "s3:delete": "bun -e \"require('fs').rmSync('data/s3', { recursive: true, force: true })\"", - "s3:logs": "docker compose -f compose.local.yml logs -f s3" + "s3:logs": "docker compose -f compose.local.yml logs -f s3", + "s3:buckets": "bun run src/server/s3/buckets.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.679.0", diff --git a/src/server/db/migrations/0000_eminent_kylun.sql b/src/server/db/migrations/0000_bitter_adam_destine.sql similarity index 97% rename from src/server/db/migrations/0000_eminent_kylun.sql rename to src/server/db/migrations/0000_bitter_adam_destine.sql index d417dafb..b703f270 100644 --- a/src/server/db/migrations/0000_eminent_kylun.sql +++ b/src/server/db/migrations/0000_bitter_adam_destine.sql @@ -1,5 +1,5 @@ CREATE TYPE "public"."locale" AS ENUM('en', 'no');--> statement-breakpoint -CREATE TYPE "public"."skill_identifiers" AS ENUM('printing', 'souldering', 'raspberry', 'unix', 'laser', 'workshop', 'microcontroller');--> statement-breakpoint +CREATE TYPE "public"."skill_identifiers" AS ENUM('printing', 'soldering', 'raspberry', 'unix', 'laser', 'workshop', 'microcontroller');--> statement-breakpoint CREATE TABLE "email_verification_requests" ( "id" text PRIMARY KEY NOT NULL, "user_id" integer NOT NULL, diff --git a/src/server/db/migrations/meta/0000_snapshot.json b/src/server/db/migrations/meta/0000_snapshot.json index 57496b40..ec55baf7 100644 --- a/src/server/db/migrations/meta/0000_snapshot.json +++ b/src/server/db/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "608a60fa-7d26-48be-a385-e5b175a7acf8", + "id": "3d626913-2220-4239-a9de-167e179fd8b5", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -416,7 +416,7 @@ "schema": "public", "values": [ "printing", - "souldering", + "soldering", "raspberry", "unix", "laser", diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json index 30ed21dd..c57026e7 100644 --- a/src/server/db/migrations/meta/_journal.json +++ b/src/server/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1737024424329, - "tag": "0000_eminent_kylun", + "when": 1737390297569, + "tag": "0000_bitter_adam_destine", "breakpoints": true } ] diff --git a/src/server/s3/buckets.ts b/src/server/s3/buckets.ts new file mode 100644 index 00000000..c6e0debb --- /dev/null +++ b/src/server/s3/buckets.ts @@ -0,0 +1,89 @@ +import { env } from '@/env'; +import { s3 } from '@/server/s3'; +import { + CreateBucketCommand, + HeadBucketCommand, + PutBucketPolicyCommand, + S3ServiceException, +} from '@aws-sdk/client-s3'; + +type BucketConfig = { + name: string; + isPublic?: boolean; +}; + +type BucketKeys = 'images'; + +type BucketsConfig = { + [K in BucketKeys]: BucketConfig; +}; + +const bucketNames = env.S3_BUCKETS.split(','); + +const buckets: BucketsConfig = { + images: { + name: bucketNames[0] as string, + isPublic: true, + }, +}; + +const getBucketConfigs = () => Object.values(buckets); + +async function setupBucket(bucketConfig: { name: string; isPublic?: boolean }) { + const { name, isPublic } = bucketConfig; + + try { + await s3.send(new HeadBucketCommand({ Bucket: name })).catch(async () => { + await s3.send( + new CreateBucketCommand({ + Bucket: name, + ...(isPublic && { ACL: 'public-read' }), + }), + ); + }); + + if (isPublic) { + await s3.send( + new PutBucketPolicyCommand({ + Bucket: name, + Policy: JSON.stringify({ + Version: '2012-10-17', + Statement: [ + { + Sid: 'PublicReadGetObject', + Effect: 'Allow', + Principal: '*', + Action: 's3:GetObject', + Resource: `arn:aws:s3:::${name}/*`, + }, + ], + }), + }), + ); + } + + console.log(`✅ Bucket ${name} configured successfully`); + } catch (error) { + if (error instanceof S3ServiceException) { + console.error(`❌ Error configuring bucket ${name}:`, error.message); + } else { + console.error(`❌ Unexpected error configuring bucket ${name}:`, error); + } + } +} + +export async function main() { + console.log('🔄 Configuring S3 buckets...'); + const bucketConfigs = getBucketConfigs(); + + await Promise.all( + bucketConfigs.filter(Boolean).map((config) => setupBucket(config)), + ); + + console.log('✅ All buckets configured successfully'); +} + +await main(); +process.exit(0); + +export { buckets, getBucketConfigs, setupBucket }; From d55b85e70df5aa11734adee604b9c1ade37ea897 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:44:48 +0100 Subject: [PATCH 93/93] typo: dockerfile --- dockerfilew => dockerfile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dockerfilew => dockerfile (100%) diff --git a/dockerfilew b/dockerfile similarity index 100% rename from dockerfilew rename to dockerfile