diff --git a/README.md b/README.md index 95bb4330..dcc6865f 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,10 @@ A detailed getting started guide can be found at [umami.is/docs](https://umami.i ### Requirements -- A server with Node.js version 18.18+. -- A PostgreSQL database version v12.14+. +- A server with Node.js version 18.18 or newer +- A database. Umami supports [PostgreSQL](https://www.postgresql.org/) (minimum v12.14) databases. -### Get the source code and install packages +### Get the Source Code and Install Packages ```bash git clone https://github.com/umami-software/umami.git @@ -58,7 +58,7 @@ postgresql://username:mypassword@localhost:5432/mydb pnpm run build ``` -The build step will create tables in your database if you are installing for the first time. It will also create a login user with username **admin** and password **umami**. +_The build step will create tables in your database if you are installing for the first time. It will also create a login user with username **admin** and password **umami**._ ### Start the Application @@ -66,36 +66,37 @@ The build step will create tables in your database if you are installing for the pnpm run start ``` -By default, this will launch the application on `http://localhost:3000`. You will need to either [proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) requests from your web server or change the [port](https://nextjs.org/docs/api-reference/cli#production) to serve the application directly. +_By default, this will launch the application on `http://localhost:3000`. You will need to either [proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) requests from your web server or change the [port](https://nextjs.org/docs/api-reference/cli#production) to serve the application directly._ --- ## 🐳 Installing with Docker -Umami provides Docker images as well as a Docker compose file for easy deployment. - -Docker image: - -```bash -docker pull docker.umami.is/umami-software/umami:latest -``` - -Docker compose to run Umami with a Postgres database, run: +To build the Umami container and start up a Postgres database, run: ```bash docker compose up -d ``` +Alternatively, to pull just the Umami Docker image with PostgreSQL support: + +```bash +docker pull docker.umami.is/umami-software/umami:latest +``` + --- ## 🔄 Getting Updates +> [!WARNING] +> If you are updating from Umami V2, image "postgresql-latest" is deprecated. You must change it to "latest". +> e.g., rename `docker.umami.is/umami-software/umami:postgresql-latest` to `docker.umami.is/umami-software/umami:latest`. To get the latest features, simply do a pull, install any new dependencies, and rebuild: ```bash git pull pnpm install -pnpm build +pnpm run build ``` To update the Docker image, simply pull the new images and rebuild: diff --git a/biome.json b/biome.json index 61d094ca..0dec793b 100644 --- a/biome.json +++ b/biome.json @@ -46,14 +46,6 @@ "arrowParentheses": "asNeeded" } }, - "css": { - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf" - } - }, "assist": { "enabled": true, "actions": { diff --git a/package.json b/package.json index 09a69135..e20ae62d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "3.0.2", + "version": "3.0.1", "description": "A modern, privacy-focused alternative to Google Analytics.", "author": "Umami Software, Inc. ", "license": "MIT", diff --git a/src/app/(main)/admin/AdminLayout.tsx b/src/app/(main)/admin/AdminLayout.tsx index 3c8fa20a..56561a8e 100644 --- a/src/app/(main)/admin/AdminLayout.tsx +++ b/src/app/(main)/admin/AdminLayout.tsx @@ -21,7 +21,6 @@ export function AdminLayout({ children }: { children: ReactNode }) { border="right" backgroundColor marginRight="2" - padding="3" > diff --git a/src/app/(main)/admin/users/[userId]/UserEditForm.tsx b/src/app/(main)/admin/users/[userId]/UserEditForm.tsx index 28bf030f..30c86862 100644 --- a/src/app/(main)/admin/users/[userId]/UserEditForm.tsx +++ b/src/app/(main)/admin/users/[userId]/UserEditForm.tsx @@ -50,7 +50,7 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () = label={formatMessage(labels.role)} rules={{ required: formatMessage(labels.required) }} > - {formatMessage(labels.viewOnly)} diff --git a/src/app/(main)/settings/SettingsLayout.tsx b/src/app/(main)/settings/SettingsLayout.tsx index f6588721..adfaaa4f 100644 --- a/src/app/(main)/settings/SettingsLayout.tsx +++ b/src/app/(main)/settings/SettingsLayout.tsx @@ -14,7 +14,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) { border="right" backgroundColor marginRight="2" - padding="3" > diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index 4e773a37..e8dfb30d 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -3,7 +3,7 @@ import { SettingsLayout } from './SettingsLayout'; export default function ({ children }) { if (process.env.cloudMode) { - return null; + //return null; } return {children}; diff --git a/src/components/common/SideMenu.tsx b/src/components/common/SideMenu.tsx index 92ff798a..bdd24952 100644 --- a/src/components/common/SideMenu.tsx +++ b/src/components/common/SideMenu.tsx @@ -51,7 +51,7 @@ export function SideMenu({ }; return ( - + {title && ( {title} diff --git a/src/components/hooks/queries/useUpdateQuery.ts b/src/components/hooks/queries/useUpdateQuery.ts index 85a94425..9535c436 100644 --- a/src/components/hooks/queries/useUpdateQuery.ts +++ b/src/components/hooks/queries/useUpdateQuery.ts @@ -1,11 +1,10 @@ import { useToast } from '@umami/react-zen'; -import type { ApiError } from '@/lib/types'; import { useApi } from '../useApi'; import { useModified } from '../useModified'; export function useUpdateQuery(path: string, params?: Record) { const { post, useMutation } = useApi(); - const query = useMutation>({ + const query = useMutation({ mutationFn: (data: Record) => post(path, { ...data, ...params }), }); const { touch } = useModified(); diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index d5bc2423..19f12d9a 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -1,6 +1,5 @@ import { FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl'; import { labels, messages } from '@/components/messages'; -import type { ApiError } from '@/lib/types'; type FormatMessage = ( descriptor: MessageDescriptor, @@ -13,7 +12,7 @@ interface UseMessages { messages: typeof messages; labels: typeof labels; getMessage: (id: string) => string; - getErrorMessage: (error: ApiError) => string | undefined; + getErrorMessage: (error: unknown) => string | undefined; FormattedMessage: typeof FormattedMessage; } @@ -26,7 +25,7 @@ export function useMessages(): UseMessages { return message ? formatMessage(message) : id; }; - const getErrorMessage = (error: ApiError) => { + const getErrorMessage = (error: unknown) => { if (!error) { return undefined; } diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 64cb870f..94970584 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -206,10 +206,6 @@ async function rawQuery(sql: string, data: Record, name?: string): return `$${params.length}${type ?? ''}`; }); - if (process.env.DATABASE_REPLICA_URL && '$replica' in client) { - return client.$replica().$queryRawUnsafe(query, ...params); - } - return client.$queryRawUnsafe(query, ...params); } @@ -300,54 +296,54 @@ function getSchema() { } function getClient() { + if (!process.env.DATABASE_URL) { + return null; + } + const url = process.env.DATABASE_URL; const replicaUrl = process.env.DATABASE_REPLICA_URL; const logQuery = process.env.LOG_QUERY; - const schema = getSchema(); - const baseAdapter = new PrismaPg({ connectionString: url }, { schema }); + const connectionUrl = new URL(url); + const schema = connectionUrl.searchParams.get('schema') ?? undefined; - const baseClient = new PrismaClient({ - adapter: baseAdapter, + const adapter = new PrismaPg({ connectionString: url.toString() }, { schema }); + + const prisma = new PrismaClient({ + adapter, errorFormat: 'pretty', ...(logQuery ? PRISMA_LOG_OPTIONS : {}), }); - if (logQuery) { - baseClient.$on('query', log); + if (replicaUrl) { + const replicaAdapter = new PrismaPg({ connectionString: replicaUrl.toString() }, { schema }); + + const replicaClient = new PrismaClient({ + adapter: replicaAdapter, + ...(logQuery ? PRISMA_LOG_OPTIONS : {}), + }); + + prisma.$extends( + readReplicas({ + replicas: [replicaClient], + }), + ); } - if (!replicaUrl) { - log('Prisma initialized'); - globalThis[PRISMA] ??= baseClient; - return baseClient; - } - - const replicaAdapter = new PrismaPg({ connectionString: replicaUrl }, { schema }); - - const replicaClient = new PrismaClient({ - adapter: replicaAdapter, - errorFormat: 'pretty', - ...(logQuery ? PRISMA_LOG_OPTIONS : {}), - }); - if (logQuery) { - replicaClient.$on('query', log); + prisma.$on('query' as never, log); } - const extended = baseClient.$extends( - readReplicas({ - replicas: [replicaClient], - }), - ); + log('Prisma initialized'); - log('Prisma initialized (with replica)'); - globalThis[PRISMA] ??= extended; + if (!globalThis[PRISMA]) { + globalThis[PRISMA] = prisma; + } - return extended; + return prisma; } -const client = (globalThis[PRISMA] || getClient()) as ReturnType; +const client: PrismaClient = globalThis[PRISMA] || getClient(); export default { client, diff --git a/src/lib/types.ts b/src/lib/types.ts index 9c061979..e727c87a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -136,8 +136,3 @@ export interface RealtimeData { urls: Record; visitors: any[]; } - -export interface ApiError extends Error { - code?: string; - message: string; -}