Skip to content

Commit

Permalink
Merge pull request #261 from cellajs/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
flipvh authored Nov 18, 2024
2 parents 1bf9294 + 7bc6848 commit fd584f0
Show file tree
Hide file tree
Showing 50 changed files with 680 additions and 392 deletions.
3 changes: 2 additions & 1 deletion backend/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defineConfig } from 'drizzle-kit';
import { dbConfig } from '#/db/db';
import { env } from './env';

const extendConfig = env.PGLITE ? { driver: 'pglite' } : {};
Expand All @@ -7,7 +8,7 @@ export default defineConfig({
schema: './src/db/schema/*',
out: './drizzle',
dialect: 'postgresql',
casing: 'snake_case',
casing: dbConfig.casing,
...extendConfig,
dbCredentials: {
url: env.PGLITE ? './.db' : env.DATABASE_URL,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { config } from 'config';
import { type DrizzleConfig, sql } from 'drizzle-orm';
import type { PgDatabase } from 'drizzle-orm/pg-core';

const dbConfig: DrizzleConfig = {
export const dbConfig: DrizzleConfig = {
logger: config.debug,
casing: 'snake_case',
};
Expand Down
2 changes: 1 addition & 1 deletion backend/src/middlewares/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { compress } from 'hono/compress';
import { cors } from 'hono/cors';
import { csrf } from 'hono/csrf';
import { secureHeaders } from 'hono/secure-headers';
import { observatoryMiddleware } from '#/middlewares/observatory/';
import { CustomHono } from '#/types/common';
import { logEvent } from './logger/log-event';
import { logger } from './logger/logger';
import { observatoryMiddleware } from './observatory-middleware';
import { rateLimiter } from './rate-limiter';

const app = new CustomHono();
Expand Down
31 changes: 0 additions & 31 deletions backend/src/middlewares/observatory-middleware.ts

This file was deleted.

18 changes: 18 additions & 0 deletions backend/src/middlewares/observatory/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { MetricOptions } from '#/middlewares/observatory/types';

// Define the metrics configuration
export const metricsConfig = {
requestDuration: {
type: 'histogram',
name: 'Request_duration',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'status', 'ok', 'route'],
buckets: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10],
},
requestsTotal: {
type: 'counter',
name: 'Requests_count',
help: 'Total number and date of requests',
labelNames: ['requestsNumber', 'date'],
},
} satisfies Record<string, MetricOptions>;
39 changes: 39 additions & 0 deletions backend/src/middlewares/observatory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { MiddlewareHandler } from 'hono/types';
import { Counter, Histogram } from 'prom-client';
import { metricsConfig } from '#/middlewares/observatory/config';

// Prometheus metrics Initialize
const observatoryRequestDurationHistogram = new Histogram({
name: metricsConfig.requestDuration.name,
help: metricsConfig.requestDuration.help,
labelNames: metricsConfig.requestDuration.labelNames,
buckets: metricsConfig.requestDuration.buckets,
});

const observatoryRequestsCounter = new Counter({
name: metricsConfig.requestsTotal.name,
help: metricsConfig.requestsTotal.help,
labelNames: metricsConfig.requestsTotal.labelNames,
});

export const observatoryMiddleware: MiddlewareHandler = async (ctx, next) => {
const start = Date.now();

// Incrementing request count
const currentCounter = await observatoryRequestsCounter.get();
observatoryRequestsCounter.inc({ requestsNumber: currentCounter.values.length + 1, date: new Date().getTime() });

// Measure request duration and record it in the histogram
const duration = (Date.now() - start) / 1000; // Convert milliseconds to seconds
observatoryRequestDurationHistogram.observe(
{
method: ctx.req.method,
status: ctx.res.status.toString(),
ok: ctx.res.status,
route: ctx.req.url,
},
duration,
);

await next();
};
7 changes: 7 additions & 0 deletions backend/src/middlewares/observatory/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Context } from 'hono';
import type { CounterConfiguration, HistogramConfiguration } from 'prom-client';

export type MetricOptions = {
disabled?: boolean;
customLabels?: Record<string, (c: Context) => string>;
} & (({ type: 'counter' } & CounterConfiguration<string>) | ({ type: 'histogram' } & HistogramConfiguration<string>));
25 changes: 16 additions & 9 deletions backend/src/modules/metrics/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
export const parsePromMetrics = (text: string): Record<string, string | number>[] => {
export const parsePromMetrics = (text: string, metricName: string): Record<string, string | number>[] => {
// Split the text into lines, trim each line, and keep only the ones starting with the metricName
const lines = text
.split('\n')
.map((line) => line.trim())
.filter((line) => line.startsWith('Requests'));
.filter((line) => line.startsWith(metricName));

// Process each line that starts with the metricName to extract labels
return lines
.map((line) => {
// Match the pattern to extract labels
const match = line.match(/Requests{([^}]*)}\s(\d+)/);
const match = line.match(/{([^}]*)}/);
if (!match) return null;

// Extract the part containing the labels from the match
const [, labels] = match;
return labels.split(',').reduce<Record<string, string>>((acc, label) => {
const [key, val] = label.split('=');
acc[key.trim()] = val.replace(/"/g, '').trim();
return acc;
}, {});

// Transform the labels into valid JSON
const jsonString = `{${labels.replace(/(\w+)=/g, '"$1":')}}`;

try {
return JSON.parse(jsonString);
} catch (err) {
return null; // If parsing fails, return null
}
})
.filter((metric) => metric !== null);
};
Expand Down
9 changes: 7 additions & 2 deletions backend/src/modules/metrics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { db } from '#/db/db';
import { getTableConfig } from 'drizzle-orm/pg-core';
import { register } from 'prom-client';
import { entityTables } from '#/entity-config';
import { metricsConfig } from '#/middlewares/observatory/config';
import { calculateRequestsPerMinute, parsePromMetrics } from '#/modules/metrics/helpers/utils';
import { CustomHono } from '#/types/common';
import MetricsRoutesConfig from './routes';
Expand All @@ -18,8 +19,12 @@ const metricRoutes = app
.openapi(MetricsRoutesConfig.getMetrics, async (ctx) => {
const metrics = await register.metrics();

const parsedMetrics = parsePromMetrics(metrics);
const requestsPerMinute = calculateRequestsPerMinute(parsedMetrics);
// get count metrics
const parsedCountMetrics = parsePromMetrics(metrics, metricsConfig.requestsTotal.name);
const requestsPerMinute = calculateRequestsPerMinute(parsedCountMetrics);

// get duration metrics
// const parsedDurationMetrics = parsePromMetrics(metrics, metricsConfig.requestDuration.name);

return ctx.json({ success: true, data: requestsPerMinute }, 200);
})
Expand Down
21 changes: 14 additions & 7 deletions backend/src/modules/organizations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,24 @@ const organizationsRoutes = app
const user = getContextUser();
const { organizationIds, subject, content } = ctx.req.valid('json');

// TODO simplify this? // For test purposes
if (typeof env.SEND_ALL_TO_EMAIL === 'string' && env.NODE_ENV === 'development') {
// For test purposes
if (env.NODE_ENV === 'development') {
const unsafeUser = await getUserBy('id', user.id, 'unsafe');
const unsubscribeToken = unsafeUser ? unsafeUser.unsubscribeToken : '';
const unsubscribeLink = `${config.backendUrl}/unsubscribe?token=${unsubscribeToken}`;
const unsubscribeLink = unsafeUser ? `${config.backendUrl}/unsubscribe?token=${unsafeUser.unsubscribeToken}` : '';

// generating email html
// Generate email HTML
const emailHtml = await render(
organizationsNewsletter({ userLanguage: user.language, subject, content, unsubscribeLink, authorEmail: user.email, orgName: 'SOME NAME' }),
organizationsNewsletter({
userLanguage: user.language,
subject,
content: user.newsletter ? content : 'You`ve unsubscribed from news letters',
unsubscribeLink,
authorEmail: user.email,
orgName: 'SOME NAME',
}),
);
emailSender.send(env.SEND_ALL_TO_EMAIL, user.newsletter ? subject : 'User unsubscribed from newsletter', emailHtml);

emailSender.send(env.SEND_ALL_TO_EMAIL ?? user.email, subject, emailHtml);
} else {
// Get members
const organizationsMembersEmails = await db
Expand Down
28 changes: 14 additions & 14 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^1.4.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@blocknote/core": "^0.19.0",
"@blocknote/react": "^0.19.0",
"@blocknote/shadcn": "^0.19.0",
"@electric-sql/client": "^0.7.1",
"@floating-ui/react": "^0.26.27",
"@blocknote/core": "^0.19.1",
"@blocknote/react": "^0.19.1",
"@blocknote/shadcn": "^0.19.1",
"@electric-sql/client": "^0.7.2",
"@floating-ui/react": "^0.26.28",
"@github/mini-throttle": "^2.1.1",
"@hookform/resolvers": "^3.9.1",
"@oslojs/encoding": "^1.1.0",
Expand Down Expand Up @@ -53,12 +53,12 @@
"@sentry/react": "^8.38.0",
"@t3-oss/env-core": "^0.11.1",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/query-sync-storage-persister": "^5.59.20",
"@tanstack/react-query": "^5.60.2",
"@tanstack/react-query-devtools": "^5.60.2",
"@tanstack/react-query-persist-client": "^5.60.2",
"@tanstack/react-router": "^1.81.5",
"@tanstack/router-devtools": "^1.81.5",
"@tanstack/query-sync-storage-persister": "^5.60.5",
"@tanstack/react-query": "^5.60.5",
"@tanstack/react-query-devtools": "^5.60.5",
"@tanstack/react-query-persist-client": "^5.60.5",
"@tanstack/react-router": "^1.81.14",
"@tanstack/router-devtools": "^1.81.14",
"@uppy/audio": "^2.0.1",
"@uppy/core": "^4.2.3",
"@uppy/dashboard": "^4.1.2",
Expand All @@ -82,7 +82,7 @@
"embla-carousel-autoplay": "^8.3.1",
"embla-carousel-react": "^8.3.1",
"emblor": "^1.4.6",
"framer-motion": "^11.11.16",
"framer-motion": "^11.11.17",
"gleap": "^14.0.8",
"hono": "4.6.10",
"i18next": "^23.16.5",
Expand Down Expand Up @@ -127,7 +127,7 @@
"@redux-devtools/extension": "^3.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@sentry/vite-plugin": "^2.22.6",
"@tanstack/router-plugin": "^1.79.0",
"@tanstack/router-plugin": "^1.81.9",
"@types/dompurify": "^3.0.5",
"@types/lodash.clonedeep": "^4.5.9",
"@types/node": "^22.9.0",
Expand All @@ -142,7 +142,7 @@
"postcss-preset-env": "^10.1.0",
"postgres": "^3.4.5",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.15",
"tsx": "^4.19.2",
"typescript": "^5.6.3",
"vite": "^5.4.11",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
}

body {
@apply bg-background text-foreground min-h-full;
@apply bg-background text-foreground min-h-full overscroll-none;
}

/* For small screens we enlarge all elements by using rem everywhere, and then simply increase font-size */
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/modules/auth/check-email-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type * as z from 'zod';

import { Button } from '~/modules/ui/button';
import { SubmitButton } from '~/modules/ui/button';
import { Form, FormControl, FormField, FormItem, FormMessage } from '~/modules/ui/form';
import { Input } from '~/modules/ui/input';

import { onlineManager } from '@tanstack/react-query';
import { config } from 'config';
import { ArrowRight } from 'lucide-react';
import { useEffect } from 'react';
import { checkEmail as baseCheckEmail } from '~/api/auth';
import { useMutation } from '~/hooks/use-mutations';
import { showToast } from '~/lib/toasts';
import type { Step, TokenData } from '.';

const formSchema = emailBodySchema;
Expand Down Expand Up @@ -44,8 +42,6 @@ export const CheckEmailForm = ({ tokenData, setStep }: CheckEmailProps) => {
});

const onSubmit = (values: z.infer<typeof formSchema>) => {
if (!onlineManager.isOnline()) return showToast(t('common:action.offline.text'), 'warning');

checkEmail(values.email);
};

Expand Down Expand Up @@ -82,10 +78,10 @@ export const CheckEmailForm = ({ tokenData, setStep }: CheckEmailProps) => {
</FormItem>
)}
/>
<Button type="submit" loading={isPending} className="w-full">
<SubmitButton loading={isPending} className="w-full">
{t('common:continue')}
<ArrowRight size={16} className="ml-2" />
</Button>
</SubmitButton>
</form>
</Form>
);
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/modules/auth/reset-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import * as z from 'zod';
import AuthPage from '~/modules/auth/auth-page';
import { Button } from '~/modules/ui/button';
import { SubmitButton } from '~/modules/ui/button';

import { onlineManager } from '@tanstack/react-query';
import { passwordSchema } from 'backend/utils/schema/common-schemas';
import { config } from 'config';
import { ArrowRight, Loader2 } from 'lucide-react';
Expand All @@ -16,7 +15,6 @@ import type { ApiError } from '~/api';
import { resetPassword as baseResetPassword } from '~/api/auth';
import { checkToken as baseCheckToken } from '~/api/general';
import { useMutation } from '~/hooks/use-mutations';
import { showToast } from '~/lib/toasts';
import { Form, FormControl, FormField, FormItem, FormMessage } from '~/modules/ui/form';
import { Input } from '~/modules/ui/input';
import { ResetPasswordRoute } from '~/routes/auth';
Expand Down Expand Up @@ -60,8 +58,6 @@ const ResetPassword = () => {

// Submit new password
const onSubmit = (values: z.infer<typeof formSchema>) => {
if (!onlineManager.isOnline()) return showToast(t('common:action.offline.text'), 'warning');

const { password } = values;
resetPassword({ token, password });
};
Expand Down Expand Up @@ -102,10 +98,10 @@ const ResetPassword = () => {
</FormItem>
)}
/>
<Button type="submit" loading={isPending} className="w-full">
<SubmitButton loading={isPending} className="w-full">
{t('common:reset')}
<ArrowRight size={16} className="ml-2" />
</Button>
</SubmitButton>
</form>
) : (
<div className="max-w-[32rem] m-4 flex flex-col items-center text-center">
Expand Down
Loading

0 comments on commit fd584f0

Please sign in to comment.