Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #254

Merged
merged 37 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6abbf41
feat: Add update functionality for attachments realtime
RomanNabukhotnyi Nov 4, 2024
ff1fb61
add: pragmatic DnD auto-scroll
LemonardoD Nov 4, 2024
559c29f
feat: Add offline prefetch throttle
RomanNabukhotnyi Nov 4, 2024
0559656
feat: Wait 1 second after init load before prefetch
RomanNabukhotnyi Nov 4, 2024
5ec24f5
feat: Close sentry to avoid sending errors when offline
RomanNabukhotnyi Nov 4, 2024
20a6629
auth helpers refactor
flipvh Nov 5, 2024
602902f
only set aspect ratio for cover or avatars
flipvh Nov 6, 2024
46499dd
implement: AttachmentsTable thumbnail click open carousel
LemonardoD Nov 6, 2024
f0d6675
refactor: Optional names for columns
RomanNabukhotnyi Nov 6, 2024
7bbee20
improve: attachment carousel & attachment item preview
LemonardoD Nov 6, 2024
bab3d80
improve: AttachmentPreview
LemonardoD Nov 6, 2024
454f516
add: support for multiple attachment
LemonardoD Nov 6, 2024
8226c17
imado fix
flipvh Nov 6, 2024
54dab03
refactor: Refactor schemas
RomanNabukhotnyi Nov 6, 2024
b4b9422
small text fix
flipvh Nov 7, 2024
16d382d
refactor: Refactor attachments sync
RomanNabukhotnyi Nov 7, 2024
e67e3e2
improve: newsletter blocknote
LemonardoD Nov 7, 2024
2d34e78
fix: drizzle generate and update migrations
LemonardoD Nov 8, 2024
b2225a6
update blocknote dep
LemonardoD Nov 8, 2024
7bdfae2
fixes: blocknote upload panel
LemonardoD Nov 8, 2024
7defd22
align blocknote with raak
LemonardoD Nov 8, 2024
ac81e1d
naming fix
LemonardoD Nov 8, 2024
faf3e97
update common types
LemonardoD Nov 8, 2024
b544b13
implement: blocknote attachments preview on alt and mouse click on fi…
LemonardoD Nov 8, 2024
e374214
fix
LemonardoD Nov 8, 2024
450addc
cella config name fork
flipvh Nov 8, 2024
4aad669
raak
flipvh Nov 8, 2024
1c170d6
be util cleanup
flipvh Nov 8, 2024
45462d9
Merge pull request #255 from cellajs/pr-branch-1731080247722
flipvh Nov 8, 2024
68a99a1
text fix
flipvh Nov 11, 2024
a79f6d4
fix: blocknote slash & format menu usage in sheet
LemonardoD Nov 11, 2024
96c2e9d
fix: org newsletter form
LemonardoD Nov 11, 2024
fe3ef28
align carousel refactor from raak
LemonardoD Nov 11, 2024
6a2d06e
feat: Electric proxy (#253)
RomanNabukhotnyi Nov 11, 2024
fcd2bda
fix: query offset on search
LemonardoD Nov 11, 2024
a06c389
fix: offset of a new query on filter by role
LemonardoD Nov 11, 2024
3af7c18
styling tweak
LemonardoD Nov 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
package-import-method=clone-or-copy
public-hoist-pattern[]=pdfjs-dist
49 changes: 49 additions & 0 deletions backend/emails/helpers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { JSDOM } from 'jsdom';

export const updateSourcesFromDataUrl = (passedHTML: string): string => {
// Parse the HTML string with JSDOM
const dom = new JSDOM(passedHTML);
const document = dom.window.document;

// Select all elements with a 'data-url' attribute within the document
const elementsWithDataUrl = document.querySelectorAll('[data-url]');

// Loop through each element with a 'data-url' attribute
for (const el of elementsWithDataUrl) {
const url = el.getAttribute('data-url');
const contentType = el.getAttribute('data-content-type');

if (!url) continue;

if (contentType === 'image') {
const imageElement = el.querySelector('img');
if (imageElement) imageElement.setAttribute('src', url);
}

// Add if email send library support video & audio
// if (contentType === 'video') {
// const videoElement = el.querySelector('video');
// if (videoElement) videoElement.setAttribute('src', url);
// }

// if (contentType === 'audio') {
// const audioElement = el.querySelector('audio');
// if (audioElement) audioElement.setAttribute('src', url);
// }

if (contentType === 'file') {
const fileLinkElement = document.createElement('a');
fileLinkElement.setAttribute('href', url);

// Set the 'download' attribute using the 'data-name' attribute or other source
const fileName = el.getAttribute('data-name') || 'file';
fileLinkElement.setAttribute('download', fileName);

// Move the original element (el) inside the <a> tag
el.parentNode?.replaceChild(fileLinkElement, el);
fileLinkElement.appendChild(el);
}
}

return document.documentElement.outerHTML;
};
3 changes: 2 additions & 1 deletion backend/emails/organization-newsletter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EmailContainer } from './components/container';
import { EmailHeader } from './components/email-header';
import { EmailReplyTo } from './components/email-reply-to';
import { Footer } from './components/footer';
import { updateSourcesFromDataUrl } from './helpers';
import type { BasicTemplateType } from './types';

interface Props extends BasicTemplateType {
Expand Down Expand Up @@ -48,7 +49,7 @@ export const organizationsNewsletter = ({ userLanguage: lng, authorEmail, conten
>
<Text>{subject}</Text>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: we need send it cos blackNote return an html*/}
<div dangerouslySetInnerHTML={{ __html: content }} />
<div dangerouslySetInnerHTML={{ __html: updateSourcesFromDataUrl(content) }} />
<Link
style={{
fontSize: '.75rem',
Expand Down
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"emails:preview": "email preview ./emails"
},
"dependencies": {
"@cellajs/imado": "^0.1.3",
"@aws-sdk/cloudfront-signer": "^3.679.0",
"@cellajs/permission-manager": "^0.1.0",
"@commander-js/extra-typings": "^12.1.0",
"@electric-sql/pglite": "^0.2.12",
Expand Down Expand Up @@ -63,6 +63,7 @@
"hono": "4.6.8",
"i18next": "^23.16.4",
"isbot": "^5.1.17",
"jsdom": "^25.0.1",
"jsonwebtoken": "^9.0.2",
"jsx-email": "^2.1.2",
"locales": "workspace:*",
Expand All @@ -83,6 +84,7 @@
},
"devDependencies": {
"@faker-js/faker": "^9.1.0",
"@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.8.6",
"@types/node-cron": "^3.0.11",
Expand Down
36 changes: 22 additions & 14 deletions backend/src/db/db.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { drizzle as pgDrizzle } from 'drizzle-orm/node-postgres';
import { type NodePgClient, drizzle as pgDrizzle } from 'drizzle-orm/node-postgres';
import { drizzle as pgliteDrizzle } from 'drizzle-orm/pglite';
import pg from 'pg';
import { env } from '#/../env';

import type { PGlite } from '@electric-sql/pglite';
import { config } from 'config';
import { sql } from 'drizzle-orm';
import { type DrizzleConfig, sql } from 'drizzle-orm';
import type { PgDatabase } from 'drizzle-orm/pg-core';

export const queryClient = env.PGLITE
? await (await import('@electric-sql/pglite')).PGlite.create({
dataDir: './.db',
})
: new pg.Pool({
connectionString: env.DATABASE_URL,
connectionTimeoutMillis: 10000,
});

const dbConfig = {
const dbConfig: DrizzleConfig = {
logger: config.debug,
casing: 'snake_case',
};

// biome-ignore lint/suspicious/noExplicitAny: Can be two different types
export const db: PgDatabase<any> = queryClient instanceof pg.Pool ? pgDrizzle(queryClient, dbConfig) : pgliteDrizzle(queryClient, dbConfig);
export const db: PgDatabase<any> & {
$client: PGlite | NodePgClient;
} = env.PGLITE
? pgliteDrizzle({
connection: {
dataDir: './.db',
},
...dbConfig,
})
: pgDrizzle({
connection: {
connectionString: env.DATABASE_URL,
connectionTimeoutMillis: 10000,
},
...dbConfig,
});

export const coalesce = <T>(column: T, value: number) => sql`COALESCE(${column}, ${value})`.mapWith(Number);
27 changes: 15 additions & 12 deletions backend/src/db/schema/attachments.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { getTableColumns } from 'drizzle-orm';
import { pgTable, timestamp, varchar } from 'drizzle-orm/pg-core';
import { nanoid } from '#/utils/nanoid';
import { organizationsTable } from './organizations';
import { usersTable } from './users';

export const attachmentsTable = pgTable('attachments', {
id: varchar('id').primaryKey().$defaultFn(nanoid),
name: varchar('name').notNull().default('attachment'),
filename: varchar('filename').notNull(),
contentType: varchar('content_type').notNull(),
size: varchar('size').notNull(),
entity: varchar('entity', { enum: ['attachment'] })
id: varchar().primaryKey().$defaultFn(nanoid),
name: varchar().notNull().default('attachment'),
filename: varchar().notNull(),
contentType: varchar().notNull(),
size: varchar().notNull(),
entity: varchar({ enum: ['attachment'] })
.notNull()
.default('attachment'),
url: varchar('url').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
createdBy: varchar('created_by').references(() => usersTable.id, {
url: varchar().notNull(),
createdAt: timestamp().defaultNow().notNull(),
createdBy: varchar().references(() => usersTable.id, {
onDelete: 'set null',
}),
modifiedAt: timestamp('modified_at'),
modifiedBy: varchar('modified_by').references(() => usersTable.id, {
modifiedAt: timestamp(),
modifiedBy: varchar().references(() => usersTable.id, {
onDelete: 'set null',
}),
organizationId: varchar('organization_id')
organizationId: varchar()
.notNull()
.references(() => organizationsTable.id, {
onDelete: 'cascade',
}),
});

export const attachmentsTableColumns = getTableColumns(attachmentsTable);

export type AttachmentModel = typeof attachmentsTable.$inferSelect;
export type InsertAttachmentModel = typeof attachmentsTable.$inferInsert;
24 changes: 12 additions & 12 deletions backend/src/db/schema/memberships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import { organizationsTable } from './organizations';
const roleEnum = config.rolesByType.entityRoles;

export const membershipsTable = pgTable('memberships', {
id: varchar('id').primaryKey().$defaultFn(nanoid),
type: varchar('type', { enum: config.contextEntityTypes }).notNull(),
userId: varchar('user_id')
id: varchar().primaryKey().$defaultFn(nanoid),
type: varchar({ enum: config.contextEntityTypes }).notNull(),
userId: varchar()
.notNull()
.references(() => usersTable.id, { onDelete: 'cascade' }),
role: varchar('role', { enum: roleEnum }).notNull().default('member'),
createdAt: timestamp('created_at').defaultNow().notNull(),
createdBy: varchar('created_by').references(() => usersTable.id, { onDelete: 'set null' }),
modifiedAt: timestamp('modified_at'),
modifiedBy: varchar('modified_by').references(() => usersTable.id, { onDelete: 'set null' }),
archived: boolean('archived').default(false).notNull(),
muted: boolean('muted').default(false).notNull(),
order: doublePrecision('sort_order').notNull(),
organizationId: varchar('organization_id')
role: varchar({ enum: roleEnum }).notNull().default('member'),
createdAt: timestamp().defaultNow().notNull(),
createdBy: varchar().references(() => usersTable.id, { onDelete: 'set null' }),
modifiedAt: timestamp(),
modifiedBy: varchar().references(() => usersTable.id, { onDelete: 'set null' }),
archived: boolean().default(false).notNull(),
muted: boolean().default(false).notNull(),
order: doublePrecision().notNull(),
organizationId: varchar()
.notNull()
.references(() => organizationsTable.id, { onDelete: 'cascade' }),
});
Expand Down
8 changes: 4 additions & 4 deletions backend/src/db/schema/oauth-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ export const supportedOauthProviders = ['github', 'google', 'microsoft'] as cons
export const oauthAccountsTable = pgTable(
'oauth_accounts',
{
providerId: varchar('provider_id', { enum: supportedOauthProviders }).notNull(),
providerUserId: varchar('provider_user_id').notNull(),
userId: varchar('user_id')
providerId: varchar({ enum: supportedOauthProviders }).notNull(),
providerUserId: varchar().notNull(),
userId: varchar()
.notNull()
.references(() => usersTable.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
createdAt: timestamp().defaultNow().notNull(),
},
(table) => {
return {
Expand Down
46 changes: 23 additions & 23 deletions backend/src/db/schema/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,31 @@ const languages = config.languages.map((lang) => lang.value) as [string, ...stri
export const organizationsTable = pgTable(
'organizations',
{
id: varchar('id').primaryKey().$defaultFn(nanoid),
entity: varchar('entity', { enum: ['organization'] })
id: varchar().primaryKey().$defaultFn(nanoid),
entity: varchar({ enum: ['organization'] })
.notNull()
.default('organization'),
name: varchar('name').notNull(),
shortName: varchar('short_name'),
slug: varchar('slug').unique().notNull(),
country: varchar('country'),
timezone: varchar('timezone'),
defaultLanguage: varchar('default_language', { enum: languages }).notNull().default(config.defaultLanguage),
languages: json('languages').$type<Language[]>().notNull().default([config.defaultLanguage]),
notificationEmail: varchar('notification_email'),
emailDomains: json('email_domains').$type<string[]>().notNull().default([]),
color: varchar('color'),
thumbnailUrl: varchar('thumbnail_url'),
bannerUrl: varchar('banner_url'),
logoUrl: varchar('logo_url'),
websiteUrl: varchar('website_url'),
welcomeText: varchar('welcome_text'),
authStrategies: json('auth_strategies').$type<string[]>().notNull().default([]),
chatSupport: boolean('chat_support').notNull().default(false),
createdAt: timestamp('created_at').defaultNow().notNull(),
createdBy: varchar('created_by').references(() => usersTable.id, { onDelete: 'set null' }),
modifiedAt: timestamp('modified_at'),
modifiedBy: varchar('modified_by').references(() => usersTable.id, { onDelete: 'set null' }),
name: varchar().notNull(),
shortName: varchar(),
slug: varchar().unique().notNull(),
country: varchar(),
timezone: varchar(),
defaultLanguage: varchar({ enum: languages }).notNull().default(config.defaultLanguage),
languages: json().$type<Language[]>().notNull().default([config.defaultLanguage]),
notificationEmail: varchar(),
emailDomains: json().$type<string[]>().notNull().default([]),
color: varchar(),
thumbnailUrl: varchar(),
bannerUrl: varchar(),
logoUrl: varchar(),
websiteUrl: varchar(),
welcomeText: varchar(),
authStrategies: json().$type<string[]>().notNull().default([]),
chatSupport: boolean().notNull().default(false),
createdAt: timestamp().defaultNow().notNull(),
createdBy: varchar().references(() => usersTable.id, { onDelete: 'set null' }),
modifiedAt: timestamp(),
modifiedBy: varchar().references(() => usersTable.id, { onDelete: 'set null' }),
},
(table) => {
return {
Expand Down
10 changes: 5 additions & 5 deletions backend/src/db/schema/passkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { usersTable } from '#/db/schema/users';
import { nanoid } from '#/utils/nanoid';

export const passkeysTable = pgTable('passkeys', {
id: varchar('id').primaryKey().$defaultFn(nanoid),
userEmail: varchar('user_email')
id: varchar().primaryKey().$defaultFn(nanoid),
userEmail: varchar()
.notNull()
.references(() => usersTable.email, { onDelete: 'cascade' }),
credentialId: varchar('credential_id').notNull(),
publicKey: varchar('public_key').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
credentialId: varchar().notNull(),
publicKey: varchar().notNull(),
createdAt: timestamp().defaultNow().notNull(),
});

export type PasskeyModel = typeof passkeysTable.$inferSelect;
Expand Down
10 changes: 5 additions & 5 deletions backend/src/db/schema/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export type RequestType = (typeof requestTypeEnum)[number];
export const requestsTable = pgTable(
'requests',
{
id: varchar('id').primaryKey().$defaultFn(nanoid),
message: varchar('message'),
email: varchar('email').notNull(),
type: varchar('type', { enum: requestTypeEnum }).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
id: varchar().primaryKey().$defaultFn(nanoid),
message: varchar(),
email: varchar().notNull(),
type: varchar({ enum: requestTypeEnum }).notNull(),
createdAt: timestamp().defaultNow().notNull(),
},
(table) => {
return {
Expand Down
22 changes: 11 additions & 11 deletions backend/src/db/schema/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ import { usersTable } from '#/db/schema/users';
export const sessionsTable = pgTable(
'sessions',
{
id: varchar('id').primaryKey(),
userId: varchar('user_id')
id: varchar().primaryKey(),
userId: varchar()
.notNull()
.references(() => usersTable.id, { onDelete: 'cascade' }),
deviceName: varchar('device_name'),
deviceType: varchar('device_type', { enum: ['desktop', 'mobile'] })
deviceName: varchar(),
deviceType: varchar({ enum: ['desktop', 'mobile'] })
.notNull()
.default('desktop'),
deviceOs: varchar('device_os'),
browser: varchar('browser'),
authStrategy: varchar('auth_strategy', {
deviceOs: varchar(),
browser: varchar(),
authStrategy: varchar({
enum: ['github', 'google', 'microsoft', 'password', 'passkey'],
}),
type: varchar('type', {
type: varchar({
enum: ['regular', 'impersonation'],
})
.notNull()
.default('regular'),
createdAt: timestamp('created_at').defaultNow().notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true, mode: 'date' }).notNull(),
adminUserId: varchar('admin_user_id').references(() => usersTable.id, { onDelete: 'cascade' }),
createdAt: timestamp().defaultNow().notNull(),
expiresAt: timestamp({ withTimezone: true, mode: 'date' }).notNull(),
adminUserId: varchar().references(() => usersTable.id, { onDelete: 'cascade' }),
},
(table) => {
return {
Expand Down
16 changes: 8 additions & 8 deletions backend/src/db/schema/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ const tokenTypeEnum = ['email_verification', 'password_reset', 'system_invitatio
const roleEnum = config.rolesByType.allRoles;

export const tokensTable = pgTable('tokens', {
id: varchar('id').primaryKey(),
type: varchar('type', { enum: tokenTypeEnum }).notNull(),
email: varchar('email'),
role: varchar('role', { enum: roleEnum }),
userId: varchar('user_id').references(() => usersTable.id, { onDelete: 'cascade' }),
organizationId: varchar('organization_id').references(() => organizationsTable.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true, mode: 'date' }).notNull(),
id: varchar().primaryKey(),
type: varchar({ enum: tokenTypeEnum }).notNull(),
email: varchar(),
role: varchar({ enum: roleEnum }),
userId: varchar().references(() => usersTable.id, { onDelete: 'cascade' }),
organizationId: varchar().references(() => organizationsTable.id, { onDelete: 'cascade' }),
createdAt: timestamp().defaultNow().notNull(),
expiresAt: timestamp({ withTimezone: true, mode: 'date' }).notNull(),
});

export type TokenModel = typeof tokensTable.$inferSelect;
Expand Down
Loading