Skip to content

Commit

Permalink
feat: setup s3, fiexed i18n and started auth
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbrusegard committed Aug 29, 2024
1 parent 7a0a8f3 commit e8f618b
Show file tree
Hide file tree
Showing 24 changed files with 275 additions and 122 deletions.
13 changes: 10 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@
# When adding additional environment variables, the schema in "/src/env.js"
# should be updated accordingly.

# Example:
# SERVERVAR="foo"
# NEXT_PUBLIC_CLIENTVAR="bar"
NODE_ENV="development"
SITE_URL="http://localhost:3000"

# Database
DATABASE_HOST="localhost"
DATABASE_PORT="5432"
DATABASE_USER="user"
DATABASE_PASSWORD="password"
DATABASE_NAME="database"

# Storage
STORAGE_HOST="localhost"
STORAGE_PORT="9000"
STORAGE_USER="user"
STORAGE_PASSWORD="password"
STORAGE_NAME="images"
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ RUN bun install --production --frozen-lockfile

COPY . .

ENV SKIP_ENV_VALIDATION=true
ENV NODE_ENV=production
RUN bun run build

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Here is a list of documentation to help you get started:

- [Drizzle](https://orm.drizzle.team/docs/overview) - ORM for interacting with the database
- [TRPC](https://trpc.io/docs) - Tool for creating API endpoints as functions
- [Lucia](https://lucia-auth.com) - Authentication library

### Other resources

Expand Down
Binary file modified bun.lockb
Binary file not shown.
20 changes: 10 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ services:
- ./data/db:/var/lib/postgresql/data
ports:
- "5432:5432"
# s3:
# image: bitnami/minio:2024
# environment:
# MINIO_ROOT_USER: ${STORAGE_USER}
# MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD}
# MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME}
# volumes:
# - ./data/s3:/bitnami/minio/data
# ports:
# - "9000:9000"
s3:
image: bitnami/minio:2024
environment:
MINIO_ROOT_USER: ${STORAGE_USER}
MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD}
MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME}
volumes:
- ./data/s3:/bitnami/minio/data
ports:
- "9000:9000"
7 changes: 2 additions & 5 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { defineConfig } from 'drizzle-kit';

import { env } from '@/env';

const connectionString = `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`;
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
schema: './src/server/db/schema/*.ts',
dialect: 'postgresql',
dbCredentials: {
url: connectionString,
url: `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`,
},
});
4 changes: 3 additions & 1 deletion next-sitemap.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { env } from '@/env';

/** @type {import('next-sitemap').IConfig} */
const config = {
siteUrl: process.env.SITE_URL ?? 'https://localhost:3000',
siteUrl: env.SITE_URL,
generateRobotsTxt: true,
generateIndexSitemap: false,
};
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
"dev": "next dev --turbo",
"lint": "biome check --write --unsafe",
"start": "next start",
"db:start": "docker-compose up db",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
"db:studio": "drizzle-kit studio",
"s3:start": "docker-compose up s3"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.637.0",
"@lucia-auth/adapter-drizzle": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
Expand All @@ -32,9 +36,10 @@
"country-flag-icons": "^1.5.12",
"cva": "^1.0.0-beta.1",
"drizzle-orm": "^0.33.0",
"lucia": "^3.2.0",
"lucide-react": "^0.396.0",
"next": "^14.2.4",
"next-intl": "^3.15.2",
"next-intl": "^3.18.1",
"next-sitemap": "^4.2.3",
"next-themes": "^0.3.0",
"nuqs": "^1.17.4",
Expand Down
6 changes: 2 additions & 4 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { getTranslations, unstable_setRequestLocale } from 'next-intl/server';
import { Inter, Montserrat } from 'next/font/google';
import { notFound } from 'next/navigation';

import { locales } from '@/lib/locale/config';
import { routing } from '@/lib/locale';
import { cx } from '@/lib/utils';

import { RootProviders } from '@/components/providers/RootProviders';
Expand All @@ -25,7 +24,7 @@ const montserrat = Montserrat({
});

export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
return routing.locales.map((locale) => ({ locale }));
}

export async function generateMetadata({
Expand Down Expand Up @@ -75,7 +74,6 @@ export default function LocaleLayout({
children,
params: { locale },
}: LocaleLayoutProps) {
if (!locales.includes(locale)) notFound();
unstable_setRequestLocale(locale);
return (
<html
Expand Down
5 changes: 2 additions & 3 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
'use client';

import { routing } from '@/lib/locale';
import { redirect, usePathname } from 'next/navigation';

import { defaultLocale } from '@/lib/locale/config';

export default function NotFound() {
const pathname = usePathname();
redirect(`/${defaultLocale}${pathname}`);
redirect(`/${routing.defaultLocale}/${pathname}`);
}
18 changes: 8 additions & 10 deletions src/components/settings/LocaleMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
'use client';

import { Globe2 } from 'lucide-react';
import { useParams } from 'next/navigation';
import * as React from 'react';

import { flagIcons, locales } from '@/lib/locale/config';
import { usePathname, useRouter } from '@/lib/locale/navigation';

import { Button } from '@/components/ui/Button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/DropdownMenu';
import { localeIcons, routing } from '@/lib/locale';
import { usePathname, useRouter } from '@/lib/locale/navigation';
import { Globe2Icon } from 'lucide-react';
import { useParams } from 'next/navigation';
import * as React from 'react';

function LocaleMenu({ t }: { t: { changeLocale: string } }) {
const router = useRouter();
Expand All @@ -23,13 +21,13 @@ function LocaleMenu({ t }: { t: { changeLocale: string } }) {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='ghost' size='icon'>
<Globe2 className='h-[1.2rem] w-[1.2rem]' />
<Globe2Icon className='h-[1.2rem] w-[1.2rem]' />
<span className='sr-only'>{t.changeLocale}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='min-w-[6rem]' align='end'>
{locales.map((locale) => {
const FlagIcon = flagIcons[locale as keyof typeof flagIcons];
{routing.locales.map((locale) => {
const FlagIcon = localeIcons[locale as keyof typeof localeIcons];
return (
<DropdownMenuItem
key={locale}
Expand Down
13 changes: 12 additions & 1 deletion src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ export const env = createEnv({
*/
server: {
NODE_ENV: z.enum(['development', 'test', 'production']),
SITE_URL: z.string(),
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(),
},

/**
Expand All @@ -30,12 +36,17 @@ export const env = createEnv({
*/
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
SITE_URL: process.env.SITE_URL,
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,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
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,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
Expand Down
35 changes: 0 additions & 35 deletions src/lib/locale/config.ts

This file was deleted.

15 changes: 10 additions & 5 deletions src/lib/locale/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { routing } from '@/lib/locale';
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';

export default getRequestConfig(async ({ locale }) => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
messages: (await import(`../../../messages/${locale}.json`))
.default as Messages,
}));
export default getRequestConfig(async ({ locale }) => {
// @ts-ignore
if (!routing.locales.includes(locale)) notFound();
return {
messages: (await import(`../../../messages/${locale}.json`))
.default as Messages,
};
});
33 changes: 33 additions & 0 deletions src/lib/locale/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { GB, NO } from 'country-flag-icons/react/1x1';
import { defineRouting } from 'next-intl/routing';

export const localeIcons = { en: GB, no: NO };

export const routing = defineRouting({
locales: ['en', 'no'],
defaultLocale: 'no',
localePrefix: 'as-needed',
pathnames: {
'/': '/',
'/events': {
en: '/events',
no: '/arrangementer',
},
'/news': {
en: '/news',
no: '/nyheter',
},
'/news/new': {
en: '/news/new',
no: '/nyheter/ny',
},
'/news/[article]': {
en: '/news/[article]',
no: '/nyheter/[article]',
},
'/about': {
en: '/about',
no: '/om-oss',
},
},
});
5 changes: 2 additions & 3 deletions src/lib/locale/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { routing } from '@/lib/locale';
import { createLocalizedPathnamesNavigation } from 'next-intl/navigation';

import { localePrefix, locales, pathnames } from '@/lib/locale/config';

export const { Link, redirect, usePathname, useRouter, getPathname } =
createLocalizedPathnamesNavigation({ locales, localePrefix, pathnames });
createLocalizedPathnamesNavigation(routing);
15 changes: 2 additions & 13 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import { routing } from '@/lib/locale';
import createMiddleware from 'next-intl/middleware';

import {
defaultLocale,
localePrefix,
locales,
pathnames,
} from '@/lib/locale/config';

export default createMiddleware({
defaultLocale,
localePrefix,
locales,
pathnames,
});
export default createMiddleware(routing);

export const config = {
matcher: ['/', '/(en|no)/:path*', '/((?!api|_next|.*\\..*).*)'],
Expand Down
41 changes: 41 additions & 0 deletions src/server/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { env } from '@/env';
import { db } from '@/server/db';
import { sessions, users } from '@/server/db/schema';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import { Lucia } from 'lucia';

const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);

export 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,
};
},
});

export * from './user';

// IMPORTANT!
declare module 'lucia' {
// Has to be an interface
interface Register {
Lucia: typeof auth;
UserId: number;
DatabaseUserAttributes: DatabaseUserAttributes;
}
}

type DatabaseUserAttributes = {
username: string;
};
Loading

0 comments on commit e8f618b

Please sign in to comment.