Initiate monorepo

This commit is contained in:
Hakan Shehu
2024-11-05 22:33:43 +01:00
parent 1a39117c73
commit 992066912e
68 changed files with 6305 additions and 5918 deletions

4
.gitignore vendored
View File

@@ -143,4 +143,6 @@ out/
.yarn/unplugged .yarn/unplugged
dist/ dist/
.DS_Store .DS_Store
.turbo
.tsbuildinfo

View File

@@ -1,3 +1,4 @@
dist dist
out out
/tsconfig.base.json /tsconfig.base.json
pnpm-lock.yaml

View File

@@ -1,6 +1,6 @@
{ {
"semi": true, "semi": true,
"trailingComma": "all", "trailingComma": "es5",
"singleQuote": true, "singleQuote": true,
"printWidth": 80, "printWidth": 80,
"tabWidth": 2 "tabWidth": 2

View File

@@ -1,34 +1,33 @@
{ {
"name": "neuron-server", "name": "@colanode/server",
"version": "1.0.0", "version": "1.0.0",
"main": "src/index.ts", "type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"files": [
"dist",
".env"
],
"scripts": { "scripts": {
"build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json", "compile": "tsc --noEmit",
"start": "node dist/index.js", "build": "tsup-node",
"dev": "node -r ts-node/register -r tsconfig-paths/register --env-file .env src/index.ts", "clean": "del-cli dist isolate tsconfig.tsbuildinfo",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\"" "lint": "eslint . --max-warnings 0",
"dev": "nodemon --env-file .env dist/index.js"
}, },
"keywords": [], "author": "Hakan Shehu",
"author": "",
"license": "ISC",
"description": "", "description": "",
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.7",
"@types/lodash": "^4.17.12",
"@types/multer": "^1.4.12", "@types/multer": "^1.4.12",
"@types/node": "^22.7.7", "@types/node": "^22.7.7",
"@types/pg": "^8.11.10", "@types/pg": "^8.11.10",
"@types/ws": "^8.5.12", "@types/ws": "^8.5.12",
"concurrently": "^9.0.1",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
"prettier": "^3.3.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsc-alias": "^1.8.10", "tsup": "^8.3.0"
"tsconfig-paths": "^4.2.0",
"typescript": "^5.6.3"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.675.0", "@aws-sdk/client-s3": "^3.675.0",
@@ -37,12 +36,12 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bullmq": "^5.21.1", "bullmq": "^5.21.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"js-sha256": "^0.11.0", "js-sha256": "^0.11.0",
"kafkajs": "^2.2.4", "kafkajs": "^2.2.4",
"kysely": "^0.27.4", "kysely": "^0.27.4",
"lodash": "^4.17.21",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"pg": "^8.13.0", "pg": "^8.13.0",
"postgres": "^3.4.4", "postgres": "^3.4.4",

View File

@@ -7,12 +7,12 @@ import {
PostgresDialect, PostgresDialect,
UpdateResult, UpdateResult,
} from 'kysely'; } from 'kysely';
import { Pool } from 'pg'; import pg from 'pg';
import { DatabaseSchema } from '@/data/schema'; import { DatabaseSchema } from '@/data/schema';
import { databaseMigrations } from '@/data/migrations'; import { databaseMigrations } from '@/data/migrations';
const dialect = new PostgresDialect({ const dialect = new PostgresDialect({
pool: new Pool({ pool: new pg.Pool({
connectionString: process.env.DATABASE_URL, connectionString: process.env.DATABASE_URL,
}), }),
}); });
@@ -40,7 +40,7 @@ export const hasInsertChanges = (result: InsertResult[]): boolean => {
} }
return result.some( return result.some(
(r) => r.numInsertedOrUpdatedRows && r.numInsertedOrUpdatedRows > 0n, (r) => r.numInsertedOrUpdatedRows && r.numInsertedOrUpdatedRows > 0n
); );
}; };

View File

@@ -3,6 +3,9 @@ import { initRedis } from '@/data/redis';
import { migrate } from '@/data/database'; import { migrate } from '@/data/database';
import { initEventWorker } from '@/queues/events'; import { initEventWorker } from '@/queues/events';
import { initTaskWorker } from '@/queues/tasks'; import { initTaskWorker } from '@/queues/tasks';
import dotenv from 'dotenv';
dotenv.config();
const init = async () => { const init = async () => {
await migrate(); await migrate();

View File

@@ -21,6 +21,9 @@ export const compareString = (a?: string | null, b?: string | null): number => {
export const getNameFromEmail = (email: string): string => { export const getNameFromEmail = (email: string): string => {
// Extract the part before the @ symbol // Extract the part before the @ symbol
const namePart = email.split('@')[0]; const namePart = email.split('@')[0];
if (!namePart) {
return '';
}
// Split by dots, underscores, and dashes, then capitalize each part // Split by dots, underscores, and dashes, then capitalize each part
const displayName = namePart const displayName = namePart

View File

@@ -16,7 +16,7 @@ import {
import { ServerNodeAttributes } from '@/types/nodes'; import { ServerNodeAttributes } from '@/types/nodes';
import { DeleteObjectCommand } from '@aws-sdk/client-s3'; import { DeleteObjectCommand } from '@aws-sdk/client-s3';
import { Job, Queue, Worker } from 'bullmq'; import { Job, Queue, Worker } from 'bullmq';
import { difference } from 'lodash'; import { difference } from 'lodash-es';
const eventQueue = new Queue('events', { const eventQueue = new Queue('events', {
connection: { connection: {
@@ -59,7 +59,7 @@ const handleEventJob = async (job: Job) => {
}; };
const handleNodeCreatedEvent = async ( const handleNodeCreatedEvent = async (
event: NodeCreatedEvent, event: NodeCreatedEvent
): Promise<void> => { ): Promise<void> => {
await createUserNodes(event); await createUserNodes(event);
await synapse.sendSynapseMessage({ await synapse.sendSynapseMessage({
@@ -70,7 +70,7 @@ const handleNodeCreatedEvent = async (
}; };
const handleNodeUpdatedEvent = async ( const handleNodeUpdatedEvent = async (
event: NodeUpdatedEvent, event: NodeUpdatedEvent
): Promise<void> => { ): Promise<void> => {
await checkForCollaboratorsChange(event); await checkForCollaboratorsChange(event);
await synapse.sendSynapseMessage({ await synapse.sendSynapseMessage({
@@ -81,7 +81,7 @@ const handleNodeUpdatedEvent = async (
}; };
const handleNodeDeletedEvent = async ( const handleNodeDeletedEvent = async (
event: NodeDeletedEvent, event: NodeDeletedEvent
): Promise<void> => { ): Promise<void> => {
if (event.attributes.type === NodeTypes.File) { if (event.attributes.type === NodeTypes.File) {
const command = new DeleteObjectCommand({ const command = new DeleteObjectCommand({
@@ -167,19 +167,19 @@ const createUserNodes = async (event: NodeCreatedEvent): Promise<void> => {
}; };
const checkForCollaboratorsChange = async ( const checkForCollaboratorsChange = async (
event: NodeUpdatedEvent, event: NodeUpdatedEvent
): Promise<void> => { ): Promise<void> => {
const beforeCollaborators = extractCollaboratorIds(event.beforeAttributes); const beforeCollaborators = extractCollaboratorIds(event.beforeAttributes);
const afterCollaborators = extractCollaboratorIds(event.afterAttributes); const afterCollaborators = extractCollaboratorIds(event.afterAttributes);
const addedCollaborators = difference( const addedCollaborators = difference(
afterCollaborators, afterCollaborators,
beforeCollaborators, beforeCollaborators
); );
const removedCollaborators = difference( const removedCollaborators = difference(
beforeCollaborators, beforeCollaborators,
afterCollaborators, afterCollaborators
); );
if (addedCollaborators.length === 0 && removedCollaborators.length === 0) { if (addedCollaborators.length === 0 && removedCollaborators.length === 0) {
@@ -198,7 +198,7 @@ const checkForCollaboratorsChange = async (
const actualAddedCollaborators = difference( const actualAddedCollaborators = difference(
addedCollaborators, addedCollaborators,
existingCollaboratorIds, existingCollaboratorIds
); );
if (actualAddedCollaborators.length > 0) { if (actualAddedCollaborators.length > 0) {
@@ -241,7 +241,7 @@ const checkForCollaboratorsChange = async (
const nodeCollaboratorIds = nodeCollaborators.map((c) => c.collaboratorId); const nodeCollaboratorIds = nodeCollaborators.map((c) => c.collaboratorId);
const actualRemovedCollaborators = difference( const actualRemovedCollaborators = difference(
removedCollaborators, removedCollaborators,
nodeCollaboratorIds, nodeCollaboratorIds
); );
if (actualRemovedCollaborators.length > 0) { if (actualRemovedCollaborators.length > 0) {

View File

@@ -170,7 +170,7 @@ workspacesRouter.post('/', async (req: NeuronRequest, res: NeuronResponse) => {
workspacesRouter.put( workspacesRouter.put(
'/:id', '/:id',
async (req: NeuronRequest, res: NeuronResponse) => { async (req: NeuronRequest, res: NeuronResponse) => {
const id = req.params.id; const id = req.params.id as string;
const input: WorkspaceInput = req.body; const input: WorkspaceInput = req.body;
if (!req.account) { if (!req.account) {
@@ -276,13 +276,13 @@ workspacesRouter.put(
}; };
return res.status(200).json(output); return res.status(200).json(output);
}, }
); );
workspacesRouter.delete( workspacesRouter.delete(
'/:id', '/:id',
async (req: NeuronRequest, res: NeuronResponse) => { async (req: NeuronRequest, res: NeuronResponse) => {
const id = req.params.id; const id = req.params.id as string;
if (!req.account) { if (!req.account) {
return res.status(401).json({ return res.status(401).json({
@@ -330,13 +330,13 @@ workspacesRouter.delete(
return res.status(200).json({ return res.status(200).json({
id: workspace.id, id: workspace.id,
}); });
}, }
); );
workspacesRouter.get( workspacesRouter.get(
'/:id', '/:id',
async (req: NeuronRequest, res: NeuronResponse) => { async (req: NeuronRequest, res: NeuronResponse) => {
const id = req.params.id; const id = req.params.id as string;
if (!req.account) { if (!req.account) {
return res.status(401).json({ return res.status(401).json({
@@ -400,7 +400,7 @@ workspacesRouter.get(
}; };
return res.status(200).json(output); return res.status(200).json(output);
}, }
); );
workspacesRouter.get('/', async (req: NeuronRequest, res: NeuronResponse) => { workspacesRouter.get('/', async (req: NeuronRequest, res: NeuronResponse) => {
@@ -430,7 +430,7 @@ workspacesRouter.get('/', async (req: NeuronRequest, res: NeuronResponse) => {
.where( .where(
'id', 'id',
'in', 'in',
workspaceUsers.map((wa) => wa.id), workspaceUsers.map((wa) => wa.id)
) )
.execute(); .execute();
@@ -438,7 +438,7 @@ workspacesRouter.get('/', async (req: NeuronRequest, res: NeuronResponse) => {
for (const workspace of workspaces) { for (const workspace of workspaces) {
const workspaceUser = workspaceUsers.find( const workspaceUser = workspaceUsers.find(
(wa) => wa.workspace_id === workspace.id, (wa) => wa.workspace_id === workspace.id
); );
if (!workspaceUser) { if (!workspaceUser) {
@@ -474,7 +474,7 @@ workspacesRouter.get('/', async (req: NeuronRequest, res: NeuronResponse) => {
workspacesRouter.post( workspacesRouter.post(
'/:id/users', '/:id/users',
async (req: NeuronRequest, res: NeuronResponse) => { async (req: NeuronRequest, res: NeuronResponse) => {
const id = req.params.id; const id = req.params.id as string;
const input: WorkspaceAccountsInviteInput = req.body; const input: WorkspaceAccountsInviteInput = req.body;
if (!input.emails || input.emails.length === 0) { if (!input.emails || input.emails.length === 0) {
@@ -545,14 +545,14 @@ workspacesRouter.post(
eb.and([ eb.and([
eb('account_id', 'in', existingAccountIds), eb('account_id', 'in', existingAccountIds),
eb('workspace_id', '=', workspace.id), eb('workspace_id', '=', workspace.id),
]), ])
) )
.execute(); .execute();
} }
if (existingWorkspaceUsers.length > 0) { if (existingWorkspaceUsers.length > 0) {
const existingUserIds = existingWorkspaceUsers.map( const existingUserIds = existingWorkspaceUsers.map(
(workspaceAccount) => workspaceAccount.id, (workspaceAccount) => workspaceAccount.id
); );
existingUsers = await database existingUsers = await database
.selectFrom('nodes') .selectFrom('nodes')
@@ -592,12 +592,12 @@ workspacesRouter.post(
} }
const existingWorkspaceUser = existingWorkspaceUsers.find( const existingWorkspaceUser = existingWorkspaceUsers.find(
(workspaceUser) => workspaceUser.account_id === account!.id, (workspaceUser) => workspaceUser.account_id === account!.id
); );
if (existingWorkspaceUser) { if (existingWorkspaceUser) {
const existingUser = existingUsers.find( const existingUser = existingUsers.find(
(user) => user.id === existingWorkspaceUser.id, (user) => user.id === existingWorkspaceUser.id
); );
if (!existingUser) { if (!existingUser) {
return res.status(500).json({ return res.status(500).json({
@@ -708,14 +708,14 @@ workspacesRouter.post(
return res.status(200).json({ return res.status(200).json({
users: users, users: users,
}); });
}, }
); );
workspacesRouter.put( workspacesRouter.put(
'/:id/users/:userId', '/:id/users/:userId',
async (req: NeuronRequest, res: NeuronResponse) => { async (req: NeuronRequest, res: NeuronResponse) => {
const id = req.params.id; const id = req.params.id as string;
const userId = req.params.userId; const userId = req.params.userId as string;
const input: WorkspaceAccountRoleUpdateInput = req.body; const input: WorkspaceAccountRoleUpdateInput = req.body;
if (!req.account) { if (!req.account) {
@@ -852,5 +852,5 @@ workspacesRouter.put(
return res.status(200).json({ return res.status(200).json({
user: userNode, user: userNode,
}); });
}, }
); );

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class BoardViewValidator implements Validator { export class BoardViewValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -29,7 +29,7 @@ export class BoardViewValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -45,7 +45,7 @@ export class BoardViewValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class CalendarViewValidator implements Validator { export class CalendarViewValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -30,7 +30,7 @@ export class CalendarViewValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -46,7 +46,7 @@ export class CalendarViewValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class ChannelValidator implements Validator { export class ChannelValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -30,7 +30,7 @@ export class ChannelValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -46,7 +46,7 @@ export class ChannelValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class FolderValidator implements Validator { export class FolderValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -30,7 +30,7 @@ export class FolderValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -46,7 +46,7 @@ export class FolderValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasCollaboratorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class MessageValidator implements Validator { export class MessageValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -26,7 +26,7 @@ export class MessageValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if ( if (
!isEqual(attributes.content, node.attributes.content) && !isEqual(attributes.content, node.attributes.content) &&
@@ -45,7 +45,7 @@ export class MessageValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
if (node.createdBy === workspaceUser.id) { if (node.createdBy === workspaceUser.id) {
return true; return true;

View File

@@ -1,6 +1,6 @@
import { SelectWorkspaceUser } from '@/data/schema'; import { SelectWorkspaceUser } from '@/data/schema';
import { hasAdminAccess, hasEditorAccess } from '@/lib/constants'; import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
@@ -8,7 +8,7 @@ import { Validator } from '@/types/validators';
export class PageValidator implements Validator { export class PageValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -30,7 +30,7 @@ export class PageValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -46,7 +46,7 @@ export class PageValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class SelectOptionValidator implements Validator { export class SelectOptionValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -30,7 +30,7 @@ export class SelectOptionValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -46,7 +46,7 @@ export class SelectOptionValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

View File

@@ -3,12 +3,12 @@ import { hasAdminAccess, hasEditorAccess } from '@/lib/constants';
import { fetchNodeRole } from '@/lib/nodes'; import { fetchNodeRole } from '@/lib/nodes';
import { ServerNode, ServerNodeAttributes } from '@/types/nodes'; import { ServerNode, ServerNodeAttributes } from '@/types/nodes';
import { Validator } from '@/types/validators'; import { Validator } from '@/types/validators';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash-es';
export class TableViewValidator implements Validator { export class TableViewValidator implements Validator {
async canCreate( async canCreate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
if (!attributes.parentId) { if (!attributes.parentId) {
return false; return false;
@@ -29,7 +29,7 @@ export class TableViewValidator implements Validator {
async canUpdate( async canUpdate(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode,
attributes: ServerNodeAttributes, attributes: ServerNodeAttributes
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {
@@ -45,7 +45,7 @@ export class TableViewValidator implements Validator {
async canDelete( async canDelete(
workspaceUser: SelectWorkspaceUser, workspaceUser: SelectWorkspaceUser,
node: ServerNode, node: ServerNode
): Promise<boolean> { ): Promise<boolean> {
const role = await fetchNodeRole(node.id, workspaceUser.id); const role = await fetchNodeRole(node.id, workspaceUser.id);
if (!role) { if (!role) {

15
apps/server/tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Server",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "../../packages/core"
}
]
}

View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
target: 'es2022',
sourcemap: true,
/**
* The common package is using the internal packages approach, so it needs to
* be transpiled / bundled together with the deployed code.
*/
noExternal: ['@colanode/core'],
/**
* Do not use tsup for generating d.ts files because it can not generate type
* the definition maps required for go-to-definition to work in our IDE. We
* use tsc for that.
*/
});

19
apps/server/turbo.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": ["//"],
"globalDependencies": [".env"],
"tasks": {
"build": {
"env": ["DEMO_ENV_VAR"],
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["src/**/*.tsx", "src/**/*.ts"]
},
"lint": {
"dependsOn": ["^lint"]
}
}
}

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@colanode",
"description": "Colanode monorepo",
"version": "1.0.0",
"private": true,
"packageManager": "pnpm@9.0.4+sha256.caa915eaae9d9aefccf50ee8aeda25a2f8684d8f9d5c6e367eaf176d97c1f89e",
"author": "Hakan Shehu",
"repository": {
"type": "git",
"url": "https://github.com/colanode/colanode"
},
"scripts": {
"compile": "turbo run compile",
"build": "turbo run build",
"clean": "turbo run clean",
"dev": "turbo run dev",
"watch": "turbo watch build --filter=@colanode/core --filter=@colanode/server",
"lint": "turbo run lint --parallel",
"test": "turbo run test -- --watch false",
"format": "prettier --write ."
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"prettier": "^3.3.3",
"turbo": "^2.1.3",
"typescript": "^5.6.3",
"vitest": "^1.6.0"
},
"dependencies": {
"lodash-es": "^4.17.21"
}
}

View File

@@ -0,0 +1,26 @@
{
"name": "@colanode/core",
"version": "1.0.0",
"type": "module",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"files": [
"src"
],
"scripts": {
"compile": "tsc --noEmit",
"test": "vitest",
"lint": "eslint . --max-warnings 0",
"build?": "No build step required. This package links directly to its source files",
"coverage": "vitest run --coverage "
},
"author": "Hakan Shehu",
"devDependencies": {
"eslint": "^8.57.1",
"prettier": "^3.3.3",
"typescript": "^5.6.3",
"vitest": "^1.6.0"
}
}

View File

@@ -0,0 +1,3 @@
export const core = () => {
return 'core library';
};

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Core Library",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"composite": true
}
}

View File

@@ -0,0 +1,13 @@
import path from 'node:path';
import { configDefaults, defineConfig } from 'vitest/config';
export default defineConfig({
test: {
exclude: [...configDefaults.exclude],
},
resolve: {
alias: {
'~': path.resolve(__dirname, './src'),
},
},
});

6016
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
packages:
- "apps/*"
- "packages/*"

5822
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"noEmit": false,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

34
tsconfig.base.json Normal file
View File

@@ -0,0 +1,34 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base Configuration",
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "esnext",
"lib": ["esnext"],
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": false,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
/* Opinion */
"incremental": true,
"tsBuildInfoFile": "${configDir}/tsconfig.tsbuildinfo",
"module": "preserve",
"outDir": "${configDir}/dist",
"baseUrl": "${configDir}",
"rootDir": "${configDir}/src",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["${configDir}/src", "${configDir}/src/**/*.json"],
"exclude": ["${configDir}/node_modules", "${configDir}/dist"]
}

24
turbo.json Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env", "**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
},
"compile": {
"dependsOn": ["^compile"]
},
"clean": {
"dependsOn": ["^clean"]
}
}
}

3
vitest.workspace.ts Normal file
View File

@@ -0,0 +1,3 @@
import { defineWorkspace } from "vitest/config";
export default defineWorkspace(["packages/*", "apps/*"]);