Fix nx lint setup (#3234)
* Fix nx lint setup * Fixes * Fixes * Add missing metadata Fixes Fix Fixes * Fix
This commit is contained in:
81
package.json
81
package.json
@ -19,86 +19,15 @@
|
|||||||
"tools/eslint-rules"
|
"tools/eslint-rules"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"patch-package": "^8.0.0",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^3.3.1",
|
|
||||||
"@graphql-codegen/client-preset": "^4.1.0",
|
|
||||||
"@graphql-codegen/typescript": "^3.0.4",
|
|
||||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
|
||||||
"@nx/eslint": "17.2.7",
|
"@nx/eslint": "17.2.7",
|
||||||
"@nx/eslint-plugin": "17.2.7",
|
"@nx/eslint-plugin": "17.2.7",
|
||||||
"@nx/jest": "17.2.7",
|
"@nx/jest": "17.2.7",
|
||||||
"@nx/js": "17.2.7",
|
"@nx/js": "17.2.7",
|
||||||
"@storybook/addon-actions": "^7.6.3",
|
"nx": "^17.2.8"
|
||||||
"@storybook/addon-coverage": "^1.0.0",
|
|
||||||
"@storybook/addon-essentials": "^7.6.3",
|
|
||||||
"@storybook/addon-interactions": "^7.6.3",
|
|
||||||
"@storybook/addon-links": "^7.6.3",
|
|
||||||
"@storybook/addon-onboarding": "^1.0.9",
|
|
||||||
"@storybook/addon-themes": "^7.6.3",
|
|
||||||
"@storybook/blocks": "^7.6.3",
|
|
||||||
"@storybook/react": "^7.6.3",
|
|
||||||
"@storybook/react-vite": "^7.6.3",
|
|
||||||
"@storybook/test": "^7.6.3",
|
|
||||||
"@storybook/test-runner": "^0.16.0",
|
|
||||||
"@stylistic/eslint-plugin": "^1.5.0",
|
|
||||||
"@swc-node/register": "~1.6.7",
|
|
||||||
"@swc/core": "~1.3.100",
|
|
||||||
"@testing-library/jest-dom": "^6.1.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@types/apollo-upload-client": "^17.0.2",
|
|
||||||
"@types/deep-equal": "^1.0.1",
|
|
||||||
"@types/jest": "^29.5.11",
|
|
||||||
"@types/js-cookie": "^3.0.3",
|
|
||||||
"@types/lodash.camelcase": "^4.3.7",
|
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
|
||||||
"@types/lodash.kebabcase": "^4.1.7",
|
|
||||||
"@types/lodash.snakecase": "^4.1.9",
|
|
||||||
"@types/luxon": "^3.3.0",
|
|
||||||
"@types/node": "20.10.0",
|
|
||||||
"@types/react": "^18.2.39",
|
|
||||||
"@types/react-datepicker": "^4.11.2",
|
|
||||||
"@types/react-dom": "^18.2.15",
|
|
||||||
"@types/scroll-into-view": "^1.16.0",
|
|
||||||
"@types/uuid": "^9.0.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
||||||
"@typescript-eslint/parser": "^6.10.0",
|
|
||||||
"@typescript-eslint/utils": "^6.9.1",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
||||||
"chromatic": "^6.18.0",
|
|
||||||
"concurrently": "^8.0.1",
|
|
||||||
"cross-var": "^1.1.0",
|
|
||||||
"dotenv-cli": "^7.2.1",
|
|
||||||
"eslint": "^8.53.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-import": "^2.27.5",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
|
||||||
"http-server": "^14.1.1",
|
|
||||||
"jest": "29.7.0",
|
|
||||||
"jest-environment-jsdom": "29.7.0",
|
|
||||||
"msw": "^2.0.11",
|
|
||||||
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
|
||||||
"nx": "17.2.7",
|
|
||||||
"prettier": "3.1.0",
|
|
||||||
"storybook": "^7.6.3",
|
|
||||||
"storybook-addon-cookie": "^3.1.0",
|
|
||||||
"storybook-addon-pseudo-states": "^2.1.2",
|
|
||||||
"ts-jest": "^29.1.1",
|
|
||||||
"ts-node": "10.9.1",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"vite": "^5.0.0",
|
|
||||||
"vite-plugin-svgr": "^4.2.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"patch-package": "^8.0.0",
|
|
||||||
"tslib": "^2.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,12 @@ prod-postgres-build:
|
|||||||
prod-postgres-run:
|
prod-postgres-run:
|
||||||
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres
|
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres
|
||||||
|
|
||||||
|
prod-website-build:
|
||||||
|
@cd ../.. && docker build -f ./packages/twenty-docker/prod/twenty-website/Dockerfile --build-arg GITHUB_TOKEN=REPLACE_ME --tag twenty-website . && cd -
|
||||||
|
|
||||||
|
prod-website-run:
|
||||||
|
@docker run -d -p 3000:3000 --name twenty-website twenty-website
|
||||||
|
|
||||||
release-front:
|
release-front:
|
||||||
@cd ../.. && docker buildx build \
|
@cd ../.. && docker buildx build \
|
||||||
--push \
|
--push \
|
||||||
|
|||||||
@ -10,7 +10,6 @@ COPY ./tools/eslint-rules /app/tools/eslint-rules
|
|||||||
COPY ./packages/twenty-server /app/packages/twenty-server
|
COPY ./packages/twenty-server /app/packages/twenty-server
|
||||||
RUN yarn
|
RUN yarn
|
||||||
|
|
||||||
RUN yarn
|
|
||||||
RUN yarn nx build twenty-server
|
RUN yarn nx build twenty-server
|
||||||
|
|
||||||
WORKDIR /app/packages/twenty-server
|
WORKDIR /app/packages/twenty-server
|
||||||
|
|||||||
30
packages/twenty-docker/prod/twenty-website/Dockerfile
Normal file
30
packages/twenty-docker/prod/twenty-website/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM node:18.17.1-alpine as twenty-website-build
|
||||||
|
|
||||||
|
ARG GITHUB_TOKEN
|
||||||
|
ARG BASE_URL
|
||||||
|
|
||||||
|
ENV GITHUB_TOKEN=$GITHUB_TOKEN
|
||||||
|
ENV BASE_URL=$BASE_URL
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./package.json .
|
||||||
|
COPY ./.eslintrc.js .
|
||||||
|
COPY ./yarn.lock .
|
||||||
|
COPY ./.yarnrc.yml .
|
||||||
|
COPY ./.yarn/releases /app/.yarn/releases
|
||||||
|
COPY ./packages/twenty-website /app/packages/twenty-website
|
||||||
|
|
||||||
|
RUN yarn
|
||||||
|
RUN yarn nx build twenty-website
|
||||||
|
|
||||||
|
FROM node:18.17.1-alpine as twenty-website
|
||||||
|
|
||||||
|
WORKDIR /app/packages/twenty-website
|
||||||
|
|
||||||
|
COPY --from=twenty-website-build /app /app
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||||
|
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the website."
|
||||||
|
|
||||||
|
CMD ["/bin/sh", "-c", "yarn nx start twenty-website"]
|
||||||
@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"nx": "NX_DEFAULT_PROJECT=twenty-docs node ../../node_modules/nx/bin/nx.js",
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
"start": "docusaurus start --host 0.0.0.0 --port 5001",
|
"start": "docusaurus start --host 0.0.0.0 --port 5001",
|
||||||
"build": "docusaurus build",
|
"build": "docusaurus build",
|
||||||
|
|||||||
@ -80,7 +80,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'storybook/no-uninstalled-addons': [
|
'storybook/no-uninstalled-addons': [
|
||||||
'error',
|
'error',
|
||||||
{ packageJsonLocation: path.resolve('../../package.json') },
|
{ packageJsonLocation: path.resolve('package.json') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"nx": "NX_DEFAULT_PROJECT=twenty-front node ../../node_modules/nx/bin/nx.js",
|
||||||
"start": "vite --host",
|
"start": "vite --host",
|
||||||
"start:clean": "yarn start --force",
|
"start:clean": "yarn start --force",
|
||||||
"build": "tsc && vite build && yarn build:inject-runtime-env",
|
"build": "tsc && vite build && yarn build:inject-runtime-env",
|
||||||
@ -94,6 +95,79 @@
|
|||||||
"xlsx-ugnis": "^0.19.3",
|
"xlsx-ugnis": "^0.19.3",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@graphql-codegen/cli": "^3.3.1",
|
||||||
|
"@graphql-codegen/client-preset": "^4.1.0",
|
||||||
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
|
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||||
|
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||||
|
"@storybook/addon-actions": "^7.6.3",
|
||||||
|
"@storybook/addon-coverage": "^1.0.0",
|
||||||
|
"@storybook/addon-essentials": "^7.6.7",
|
||||||
|
"@storybook/addon-interactions": "^7.6.7",
|
||||||
|
"@storybook/addon-links": "^7.6.7",
|
||||||
|
"@storybook/addon-onboarding": "^1.0.10",
|
||||||
|
"@storybook/addon-themes": "^7.6.7",
|
||||||
|
"@storybook/blocks": "^7.6.3",
|
||||||
|
"@storybook/react": "^7.6.3",
|
||||||
|
"@storybook/react-vite": "^7.6.3",
|
||||||
|
"@storybook/test": "^7.6.3",
|
||||||
|
"@storybook/test-runner": "^0.16.0",
|
||||||
|
"@stylistic/eslint-plugin": "^1.5.0",
|
||||||
|
"@swc-node/register": "~1.6.7",
|
||||||
|
"@swc/core": "~1.3.100",
|
||||||
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@types/apollo-upload-client": "^17.0.2",
|
||||||
|
"@types/deep-equal": "^1.0.1",
|
||||||
|
"@types/jest": "^29.5.11",
|
||||||
|
"@types/js-cookie": "^3.0.3",
|
||||||
|
"@types/lodash.camelcase": "^4.3.7",
|
||||||
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
|
"@types/lodash.kebabcase": "^4.1.7",
|
||||||
|
"@types/lodash.snakecase": "^4.1.9",
|
||||||
|
"@types/luxon": "^3.3.0",
|
||||||
|
"@types/node": "^20.10.6",
|
||||||
|
"@types/react": "^18.2.39",
|
||||||
|
"@types/react-datepicker": "^4.11.2",
|
||||||
|
"@types/react-dom": "^18.2.15",
|
||||||
|
"@types/scroll-into-view": "^1.16.0",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||||
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
|
"@typescript-eslint/utils": "^6.9.1",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
|
"chromatic": "^6.18.0",
|
||||||
|
"concurrently": "^8.0.1",
|
||||||
|
"cross-var": "^1.1.0",
|
||||||
|
"dotenv-cli": "^7.2.1",
|
||||||
|
"eslint": "^8.53.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
|
"eslint-plugin-unused-imports": "^3.0.0",
|
||||||
|
"http-server": "^14.1.1",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"jest-environment-jsdom": "29.7.0",
|
||||||
|
"msw": "^2.0.11",
|
||||||
|
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
||||||
|
"prettier": "3.1.0",
|
||||||
|
"storybook": "^7.6.3",
|
||||||
|
"storybook-addon-cookie": "^3.2.0",
|
||||||
|
"storybook-addon-pseudo-states": "^2.1.2",
|
||||||
|
"ts-jest": "^29.1.1",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vite-plugin-svgr": "^4.2.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.16.0",
|
"node": "^18.16.0",
|
||||||
"npm": "please-use-yarn",
|
"npm": "please-use-yarn",
|
||||||
|
|||||||
@ -182,6 +182,12 @@ export enum FieldMetadataType {
|
|||||||
Uuid = 'UUID'
|
Uuid = 'UUID'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FullName = {
|
||||||
|
__typename?: 'FullName';
|
||||||
|
firstName: Scalars['String']['output'];
|
||||||
|
lastName: Scalars['String']['output'];
|
||||||
|
};
|
||||||
|
|
||||||
export type IdFilterComparison = {
|
export type IdFilterComparison = {
|
||||||
eq?: InputMaybe<Scalars['ID']['input']>;
|
eq?: InputMaybe<Scalars['ID']['input']>;
|
||||||
gt?: InputMaybe<Scalars['ID']['input']>;
|
gt?: InputMaybe<Scalars['ID']['input']>;
|
||||||
@ -389,6 +395,11 @@ export enum RelationMetadataType {
|
|||||||
OneToOne = 'ONE_TO_ONE'
|
OneToOne = 'ONE_TO_ONE'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Sentry = {
|
||||||
|
__typename?: 'Sentry';
|
||||||
|
dsn: Scalars['String']['output'];
|
||||||
|
};
|
||||||
|
|
||||||
export type Support = {
|
export type Support = {
|
||||||
__typename?: 'Support';
|
__typename?: 'Support';
|
||||||
supportDriver: Scalars['String']['output'];
|
supportDriver: Scalars['String']['output'];
|
||||||
@ -454,7 +465,7 @@ export type User = {
|
|||||||
lastName: Scalars['String']['output'];
|
lastName: Scalars['String']['output'];
|
||||||
passwordHash?: Maybe<Scalars['String']['output']>;
|
passwordHash?: Maybe<Scalars['String']['output']>;
|
||||||
updatedAt: Scalars['DateTime']['output'];
|
updatedAt: Scalars['DateTime']['output'];
|
||||||
workspaceMember: UserWorkspaceMember;
|
workspaceMember: WorkspaceMember;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserEdge = {
|
export type UserEdge = {
|
||||||
@ -465,21 +476,6 @@ export type UserEdge = {
|
|||||||
node: User;
|
node: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserWorkspaceMember = {
|
|
||||||
__typename?: 'UserWorkspaceMember';
|
|
||||||
avatarUrl?: Maybe<Scalars['String']['output']>;
|
|
||||||
colorScheme: Scalars['String']['output'];
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
locale: Scalars['String']['output'];
|
|
||||||
name: UserWorkspaceMemberName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UserWorkspaceMemberName = {
|
|
||||||
__typename?: 'UserWorkspaceMemberName';
|
|
||||||
firstName: Scalars['String']['output'];
|
|
||||||
lastName: Scalars['String']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Workspace = {
|
export type Workspace = {
|
||||||
__typename?: 'Workspace';
|
__typename?: 'Workspace';
|
||||||
allowImpersonation: Scalars['Boolean']['output'];
|
allowImpersonation: Scalars['Boolean']['output'];
|
||||||
@ -502,6 +498,15 @@ export type WorkspaceEdge = {
|
|||||||
node: Workspace;
|
node: Workspace;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMember = {
|
||||||
|
__typename?: 'WorkspaceMember';
|
||||||
|
avatarUrl?: Maybe<Scalars['String']['output']>;
|
||||||
|
colorScheme: Scalars['String']['output'];
|
||||||
|
id: Scalars['ID']['output'];
|
||||||
|
locale: Scalars['String']['output'];
|
||||||
|
name: FullName;
|
||||||
|
};
|
||||||
|
|
||||||
export type Field = {
|
export type Field = {
|
||||||
__typename?: 'field';
|
__typename?: 'field';
|
||||||
createdAt: Scalars['DateTime']['output'];
|
createdAt: Scalars['DateTime']['output'];
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"nx": "NX_DEFAULT_PROJECT=twenty-server node ../../node_modules/nx/bin/nx.js",
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "yarn prebuild && nest build",
|
"build": "yarn prebuild && nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
@ -17,9 +18,9 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register ../../node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "./scripts/run-integration.sh",
|
"test:e2e": "./scripts/run-integration.sh",
|
||||||
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
|
"typeorm": "ts-node -r tsconfig-paths/register ../../node_modules/typeorm/cli.js",
|
||||||
"typeorm:migrate": "yarn typeorm migration:run -d ./src/database/typeorm/metadata/metadata.datasource.ts && yarn typeorm migration:run -d ./src/database/typeorm/core/core.datasource.ts",
|
"typeorm:migrate": "yarn typeorm migration:run -d ./src/database/typeorm/metadata/metadata.datasource.ts && yarn typeorm migration:run -d ./src/database/typeorm/core/core.datasource.ts",
|
||||||
"database:init": "yarn database:setup && yarn database:seed:dev",
|
"database:init": "yarn database:setup && yarn database:seed:dev",
|
||||||
"database:setup": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate",
|
"database:setup": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate",
|
||||||
@ -131,8 +132,7 @@
|
|||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
"ts-loader": "^9.2.3",
|
"ts-loader": "^9.2.3"
|
||||||
"tsconfig-paths": "4.1.0"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"graphql": "16.8.0"
|
"graphql": "16.8.0"
|
||||||
|
|||||||
7
packages/twenty-website/.eslintrc.js
Normal file
7
packages/twenty-website/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['../../.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
'prefer-arrow/prefer-arrow-functions': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"next/core-web-vitals",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:prettier/recommended"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"endOfLine": "auto"
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ This is not related in anyway to the main app, which you can find in twenty-fron
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
We're using Nest.JS
|
We're using Next.JS
|
||||||
|
|
||||||
From the root directory:
|
From the root directory:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
@ -14,14 +15,18 @@
|
|||||||
"@jsdevtools/rehype-toc": "^3.0.2",
|
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||||
"@nivo/calendar": "^0.84.0",
|
"@nivo/calendar": "^0.84.0",
|
||||||
"@nivo/core": "^0.84.0",
|
"@nivo/core": "^0.84.0",
|
||||||
|
"@nx/eslint-plugin": "^17.2.8",
|
||||||
|
"@octokit/graphql": "^7.0.2",
|
||||||
"@stoplight/elements": "^7.16.2",
|
"@stoplight/elements": "^7.16.2",
|
||||||
"@tabler/icons-react": "^2.44.0",
|
"@tabler/icons-react": "^2.44.0",
|
||||||
"better-sqlite3": "^9.2.2",
|
"better-sqlite3": "^9.2.2",
|
||||||
"graphiql": "^3.0.10",
|
"graphiql": "^3.0.10",
|
||||||
|
"graphql": "^16.8.1",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-mdx-remote": "^4.4.1",
|
"next-mdx-remote": "^4.4.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-icons": "^4.12.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-behead": "^3.1.0",
|
"remark-behead": "^3.1.0",
|
||||||
"remark-gfm": "^3.0.1"
|
"remark-gfm": "^3.0.1"
|
||||||
|
|||||||
@ -1,65 +1,68 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
login: string;
|
login: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const AvatarGridContainer = styled.div`
|
const AvatarGridContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AvatarItem = styled.div`
|
const AvatarItem = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
.username {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
height: auto;
|
bottom: 0;
|
||||||
display: block;
|
left: 0;
|
||||||
}
|
width: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
visibility 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
.username {
|
&:hover .username {
|
||||||
position: absolute;
|
visibility: visible;
|
||||||
bottom: 0;
|
opacity: 1;
|
||||||
left: 0;
|
}
|
||||||
width: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease, visibility 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .username {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
const AvatarGrid = ({ users }: { users: User[] }) => {
|
const AvatarGrid = ({ users }: { users: User[] }) => {
|
||||||
return (
|
return (
|
||||||
<AvatarGridContainer>
|
<AvatarGridContainer>
|
||||||
{users.map(user => (
|
{users.map((user) => (
|
||||||
<Link href={`/developers/contributors/${user.login}`} key={`l_${user.login}`}>
|
<Link
|
||||||
<AvatarItem key={user.login}>
|
href={`/developers/contributors/${user.login}`}
|
||||||
<img src={user.avatarUrl} alt={user.login} />
|
key={`l_${user.login}`}
|
||||||
<span className="username">{user.login}</span>
|
>
|
||||||
</AvatarItem>
|
<AvatarItem key={user.login}>
|
||||||
</Link>
|
<img src={user.avatarUrl} alt={user.login} />
|
||||||
))}
|
<span className="username">{user.login}</span>
|
||||||
</AvatarGridContainer>
|
</AvatarItem>
|
||||||
);
|
</Link>
|
||||||
|
))}
|
||||||
|
</AvatarGridContainer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AvatarGrid;
|
export default AvatarGrid;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Logo } from './Logo';
|
|
||||||
import { DiscordIcon, GithubIcon2, LinkedInIcon, XIcon } from './Icons';
|
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
import { DiscordIcon, GithubIcon2, LinkedInIcon, XIcon } from './Icons';
|
||||||
|
import { Logo } from './Logo';
|
||||||
|
|
||||||
const FooterContainer = styled.div`
|
const FooterContainer = styled.div`
|
||||||
padding: 64px 96px 64px 96px;
|
padding: 64px 96px 64px 96px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Logo } from './Logo';
|
|
||||||
import { IBM_Plex_Mono } from 'next/font/google';
|
import { IBM_Plex_Mono } from 'next/font/google';
|
||||||
import { DiscordIcon, GithubIcon, GithubIcon2, XIcon } from './Icons';
|
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
import { ExternalArrow } from '@/app/components/ExternalArrow';
|
import { ExternalArrow } from '@/app/components/ExternalArrow';
|
||||||
|
|
||||||
|
import { DiscordIcon, GithubIcon, GithubIcon2, XIcon } from './Icons';
|
||||||
|
import { Logo } from './Logo';
|
||||||
|
|
||||||
const IBMPlexMono = IBM_Plex_Mono({
|
const IBMPlexMono = IBM_Plex_Mono({
|
||||||
weight: '500',
|
weight: '500',
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { Logo } from './Logo';
|
|
||||||
import { IBM_Plex_Mono } from 'next/font/google';
|
|
||||||
import { GithubIcon } from './Icons';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IBM_Plex_Mono } from 'next/font/google';
|
||||||
|
|
||||||
import { ExternalArrow } from '@/app/components/ExternalArrow';
|
import { ExternalArrow } from '@/app/components/ExternalArrow';
|
||||||
|
|
||||||
|
import { GithubIcon } from './Icons';
|
||||||
|
import { Logo } from './Logo';
|
||||||
|
|
||||||
const IBMPlexMono = IBM_Plex_Mono({
|
const IBMPlexMono = IBM_Plex_Mono({
|
||||||
weight: '500',
|
weight: '500',
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
import React, { ReactNode, useState } from 'react';
|
||||||
|
|
||||||
import TokenForm, {
|
import TokenForm, {
|
||||||
TokenFormProps,
|
TokenFormProps,
|
||||||
} from '@/app/components/PlaygroundTokenForm';
|
} from '@/app/components/PlaygroundTokenForm';
|
||||||
import React, { ReactNode, useState } from 'react';
|
|
||||||
|
|
||||||
type PlaygroundProps = TokenFormProps & {
|
type PlaygroundProps = TokenFormProps & {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { parseJson } from 'nx/src/utils/json';
|
|
||||||
import { TbLoader2 } from 'react-icons/tb';
|
import { TbLoader2 } from 'react-icons/tb';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { parseJson } from 'nx/src/utils/json';
|
||||||
|
|
||||||
export type TokenFormProps = {
|
export type TokenFormProps = {
|
||||||
setOpenApiJson?: (json: object) => void;
|
setOpenApiJson?: (json: object) => void;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import { ResponsiveTimeRange } from '@nivo/calendar'
|
import { ResponsiveTimeRange } from '@nivo/calendar';
|
||||||
|
|
||||||
export const ActivityLog = ({ data }: { data: { value: number, day: string }[] }) => {
|
export const ActivityLog = ({
|
||||||
return <ResponsiveTimeRange
|
data,
|
||||||
data={data}
|
}: {
|
||||||
/>;
|
data: { value: number; day: string }[];
|
||||||
}
|
}) => {
|
||||||
|
return <ResponsiveTimeRange data={data} />;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import Image from 'next/image';
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
import AvatarGrid from '@/app/components/AvatarGrid';
|
|
||||||
import { ResponsiveTimeRange } from '@nivo/calendar'
|
|
||||||
import { ActivityLog } from './components/ActivityLog';
|
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
import { ActivityLog } from './components/ActivityLog';
|
||||||
|
|
||||||
interface Contributor {
|
interface Contributor {
|
||||||
login: string;
|
login: string;
|
||||||
@ -11,21 +10,22 @@ interface Contributor {
|
|||||||
pullRequestCount: number;
|
pullRequestCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateMetadata({
|
||||||
export function generateMetadata({ params }: { params: { slug: string } }): Metadata {
|
params,
|
||||||
return {
|
}: {
|
||||||
title: params.slug + ' | Contributors',
|
params: { slug: string };
|
||||||
};
|
}): Metadata {
|
||||||
|
return {
|
||||||
|
title: params.slug + ' | Contributors',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: { slug: string } }) {
|
||||||
|
const db = new Database('db.sqlite', { readonly: true });
|
||||||
|
|
||||||
|
const contributor = db
|
||||||
export default async function (
|
.prepare(
|
||||||
{ params }: { params: { slug: string } }) {
|
`
|
||||||
|
|
||||||
const db = new Database('db.sqlite', { readonly: true });
|
|
||||||
|
|
||||||
const contributor = db.prepare(`
|
|
||||||
SELECT
|
SELECT
|
||||||
u.login,
|
u.login,
|
||||||
u.avatarUrl,
|
u.avatarUrl,
|
||||||
@ -35,9 +35,13 @@ export default async function (
|
|||||||
users u
|
users u
|
||||||
WHERE
|
WHERE
|
||||||
u.login = :user_id
|
u.login = :user_id
|
||||||
`).get({'user_id' : params.slug}) as Contributor;
|
`,
|
||||||
|
)
|
||||||
|
.get({ user_id: params.slug }) as Contributor;
|
||||||
|
|
||||||
const pullRequestActivity = db.prepare(`
|
const pullRequestActivity = db
|
||||||
|
.prepare(
|
||||||
|
`
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) as value,
|
COUNT(*) as value,
|
||||||
DATE(createdAt) as day
|
DATE(createdAt) as day
|
||||||
@ -49,11 +53,13 @@ const pullRequestActivity = db.prepare(`
|
|||||||
DATE(createdAt)
|
DATE(createdAt)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
DATE(createdAt)
|
DATE(createdAt)
|
||||||
`).all({'user_id': params.slug}) as { value: number, day: string }[];
|
`,
|
||||||
|
)
|
||||||
|
.all({ user_id: params.slug }) as { value: number; day: string }[];
|
||||||
|
|
||||||
|
const pullRequestList = db
|
||||||
|
.prepare(
|
||||||
const pullRequestList = db.prepare(`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
@ -69,32 +75,46 @@ const pullRequestList = db.prepare(`
|
|||||||
authorId = (SELECT id FROM users WHERE login = :user_id)
|
authorId = (SELECT id FROM users WHERE login = :user_id)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
DATE(createdAt) DESC
|
DATE(createdAt) DESC
|
||||||
`).all({'user_id': params.slug}) as { title: string, createdAt: string, url: string }[];
|
`,
|
||||||
|
)
|
||||||
|
.all({ user_id: params.slug }) as {
|
||||||
|
title: string;
|
||||||
|
createdAt: string;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{maxWidth: '900px', display: 'flex', padding: '40px', gap: '24px'}}>
|
<div
|
||||||
<div style={{ flexDirection: 'column', width: '240px'}}>
|
style={{
|
||||||
<Image src={contributor.avatarUrl} alt={contributor.login} width={240} height={240} />
|
maxWidth: '900px',
|
||||||
|
display: 'flex',
|
||||||
|
padding: '40px',
|
||||||
|
gap: '24px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ flexDirection: 'column', width: '240px' }}>
|
||||||
|
<Image
|
||||||
|
src={contributor.avatarUrl}
|
||||||
|
alt={contributor.login}
|
||||||
|
width={240}
|
||||||
|
height={240}
|
||||||
|
/>
|
||||||
<h1>{contributor.login}</h1>
|
<h1>{contributor.login}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div style={{flexDirection: 'column'}}>
|
<div style={{ flexDirection: 'column' }}>
|
||||||
<div style={{width: '450px', height: '200px'}}>
|
<div style={{ width: '450px', height: '200px' }}>
|
||||||
<ActivityLog
|
<ActivityLog data={pullRequestActivity} />
|
||||||
data={pullRequestActivity}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{width: '450px'}}>
|
|
||||||
{pullRequestList.map(pr => (
|
|
||||||
<div>
|
|
||||||
<a href={pr.url}>{pr.title}</a>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ width: '450px' }}>
|
||||||
|
{pullRequestList.map((pr) => (
|
||||||
|
<div>
|
||||||
|
<a href={pr.url}>{pr.title}</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -1,19 +1,23 @@
|
|||||||
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: { slug: string } }) {
|
{ params }: { params: { slug: string } },
|
||||||
const db = new Database('db.sqlite', { readonly: true });
|
) {
|
||||||
|
const db = new Database('db.sqlite', { readonly: true });
|
||||||
|
|
||||||
if(params.slug !== 'users' && params.slug !== 'labels' && params.slug !== 'pullRequests' && params.slug !== 'issues') {
|
if (
|
||||||
return Response.json({ error: 'Invalid table name' }, { status: 400 });
|
params.slug !== 'users' &&
|
||||||
}
|
params.slug !== 'labels' &&
|
||||||
|
params.slug !== 'pullRequests' &&
|
||||||
|
params.slug !== 'issues'
|
||||||
|
) {
|
||||||
|
return Response.json({ error: 'Invalid table name' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const rows = db.prepare('SELECT * FROM ' + params.slug).all();
|
const rows = db.prepare('SELECT * FROM ' + params.slug).all();
|
||||||
|
|
||||||
db.close();
|
|
||||||
|
|
||||||
return Response.json(rows);
|
db.close();
|
||||||
|
|
||||||
|
return Response.json(rows);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Database from 'better-sqlite3';
|
|
||||||
import { graphql } from '@octokit/graphql';
|
import { graphql } from '@octokit/graphql';
|
||||||
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
const db = new Database('db.sqlite', { verbose: console.log });
|
const db = new Database('db.sqlite', { verbose: console.log });
|
||||||
|
|
||||||
@ -83,8 +83,13 @@ const query = graphql.defaults({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchData(cursor: string | null = null, isIssues: boolean = false, accumulatedData: Array<PullRequestNode | IssueNode> = []): Promise<Array<PullRequestNode | IssueNode>> {
|
async function fetchData(
|
||||||
const { repository } = await query<RepoData>(`
|
cursor: string | null = null,
|
||||||
|
isIssues: boolean = false,
|
||||||
|
accumulatedData: Array<PullRequestNode | IssueNode> = [],
|
||||||
|
): Promise<Array<PullRequestNode | IssueNode>> {
|
||||||
|
const { repository } = await query<RepoData>(
|
||||||
|
`
|
||||||
query ($cursor: String) {
|
query ($cursor: String) {
|
||||||
repository(owner: "twentyhq", name: "twenty") {
|
repository(owner: "twentyhq", name: "twenty") {
|
||||||
pullRequests(first: 100, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: ${isIssues}) {
|
pullRequests(first: 100, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: ${isIssues}) {
|
||||||
@ -148,10 +153,17 @@ async function fetchData(cursor: string | null = null, isIssues: boolean = false
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, { cursor });
|
`,
|
||||||
|
{ cursor },
|
||||||
|
);
|
||||||
|
|
||||||
const newAccumulatedData: Array<PullRequestNode | IssueNode> = [...accumulatedData, ...(isIssues ? repository.issues.nodes : repository.pullRequests.nodes)];
|
const newAccumulatedData: Array<PullRequestNode | IssueNode> = [
|
||||||
const pageInfo = isIssues ? repository.issues.pageInfo : repository.pullRequests.pageInfo;
|
...accumulatedData,
|
||||||
|
...(isIssues ? repository.issues.nodes : repository.pullRequests.nodes),
|
||||||
|
];
|
||||||
|
const pageInfo = isIssues
|
||||||
|
? repository.issues.pageInfo
|
||||||
|
: repository.pullRequests.pageInfo;
|
||||||
|
|
||||||
if (pageInfo.hasNextPage) {
|
if (pageInfo.hasNextPage) {
|
||||||
return fetchData(pageInfo.endCursor, isIssues, newAccumulatedData);
|
return fetchData(pageInfo.endCursor, isIssues, newAccumulatedData);
|
||||||
@ -173,11 +185,12 @@ async function fetchAssignableUsers(): Promise<Set<string>> {
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
return new Set(repository.assignableUsers.nodes.map(user => user.login));
|
return new Set(repository.assignableUsers.nodes.map((user) => user.login));
|
||||||
}
|
}
|
||||||
|
|
||||||
const initDb = () => {
|
const initDb = () => {
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS pullRequests (
|
CREATE TABLE IF NOT EXISTS pullRequests (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
@ -190,9 +203,11 @@ const initDb = () => {
|
|||||||
authorId TEXT,
|
authorId TEXT,
|
||||||
FOREIGN KEY (authorId) REFERENCES users(id)
|
FOREIGN KEY (authorId) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS issues (
|
CREATE TABLE IF NOT EXISTS issues (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
@ -204,9 +219,11 @@ const initDb = () => {
|
|||||||
authorId TEXT,
|
authorId TEXT,
|
||||||
FOREIGN KEY (authorId) REFERENCES users(id)
|
FOREIGN KEY (authorId) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
login TEXT,
|
login TEXT,
|
||||||
@ -214,81 +231,133 @@ const initDb = () => {
|
|||||||
url TEXT,
|
url TEXT,
|
||||||
isEmployee BOOLEAN
|
isEmployee BOOLEAN
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS labels (
|
CREATE TABLE IF NOT EXISTS labels (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
color TEXT,
|
color TEXT,
|
||||||
description TEXT
|
description TEXT
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS pullRequestLabels (
|
CREATE TABLE IF NOT EXISTS pullRequestLabels (
|
||||||
pullRequestId TEXT,
|
pullRequestId TEXT,
|
||||||
labelId TEXT,
|
labelId TEXT,
|
||||||
FOREIGN KEY (pullRequestId) REFERENCES pullRequests(id),
|
FOREIGN KEY (pullRequestId) REFERENCES pullRequests(id),
|
||||||
FOREIGN KEY (labelId) REFERENCES labels(id)
|
FOREIGN KEY (labelId) REFERENCES labels(id)
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(
|
||||||
|
`
|
||||||
CREATE TABLE IF NOT EXISTS issueLabels (
|
CREATE TABLE IF NOT EXISTS issueLabels (
|
||||||
issueId TEXT,
|
issueId TEXT,
|
||||||
labelId TEXT,
|
labelId TEXT,
|
||||||
FOREIGN KEY (issueId) REFERENCES issues(id),
|
FOREIGN KEY (issueId) REFERENCES issues(id),
|
||||||
FOREIGN KEY (labelId) REFERENCES labels(id)
|
FOREIGN KEY (labelId) REFERENCES labels(id)
|
||||||
);
|
);
|
||||||
`).run();
|
`,
|
||||||
|
).run();
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
|
initDb();
|
||||||
|
|
||||||
initDb();
|
// TODO if we ever hit API Rate Limiting
|
||||||
|
const lastPRCursor = null;
|
||||||
|
const lastIssueCursor = null;
|
||||||
|
|
||||||
// TODO if we ever hit API Rate Limiting
|
const assignableUsers = await fetchAssignableUsers();
|
||||||
const lastPRCursor = null;
|
const prs = (await fetchData(lastPRCursor)) as Array<PullRequestNode>;
|
||||||
const lastIssueCursor = null;
|
const issues = (await fetchData(lastIssueCursor, true)) as Array<IssueNode>;
|
||||||
|
|
||||||
const assignableUsers = await fetchAssignableUsers();
|
const insertPR = db.prepare(
|
||||||
const prs = await fetchData(lastPRCursor) as Array<PullRequestNode>;
|
'INSERT INTO pullRequests (id, title, body, url, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
|
||||||
const issues = await fetchData(lastIssueCursor, true) as Array<IssueNode>;
|
);
|
||||||
|
const insertIssue = db.prepare(
|
||||||
const insertPR = db.prepare('INSERT INTO pullRequests (id, title, body, url, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
'INSERT INTO issues (id, title, body, url, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
|
||||||
const insertIssue = db.prepare('INSERT INTO issues (id, title, body, url, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
);
|
||||||
const insertUser = db.prepare('INSERT INTO users (id, login, avatarUrl, url, isEmployee) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
const insertUser = db.prepare(
|
||||||
const insertLabel = db.prepare('INSERT INTO labels (id, name, color, description) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
'INSERT INTO users (id, login, avatarUrl, url, isEmployee) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
|
||||||
const insertPullRequestLabel = db.prepare('INSERT INTO pullRequestLabels (pullRequestId, labelId) VALUES (?, ?)');
|
);
|
||||||
const insertIssueLabel = db.prepare('INSERT INTO issueLabels (issueId, labelId) VALUES (?, ?)');
|
const insertLabel = db.prepare(
|
||||||
|
'INSERT INTO labels (id, name, color, description) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
|
||||||
|
);
|
||||||
|
const insertPullRequestLabel = db.prepare(
|
||||||
|
'INSERT INTO pullRequestLabels (pullRequestId, labelId) VALUES (?, ?)',
|
||||||
|
);
|
||||||
|
const insertIssueLabel = db.prepare(
|
||||||
|
'INSERT INTO issueLabels (issueId, labelId) VALUES (?, ?)',
|
||||||
|
);
|
||||||
|
|
||||||
for (const pr of prs) {
|
for (const pr of prs) {
|
||||||
console.log(pr);
|
console.log(pr);
|
||||||
if(pr.author == null) { continue; }
|
if (pr.author == null) {
|
||||||
insertUser.run(pr.author.resourcePath, pr.author.login, pr.author.avatarUrl, pr.author.url, assignableUsers.has(pr.author.login) ? 1 : 0);
|
continue;
|
||||||
insertPR.run(pr.id, pr.title, pr.body, pr.url, pr.createdAt, pr.updatedAt, pr.closedAt, pr.mergedAt, pr.author.resourcePath);
|
}
|
||||||
|
insertUser.run(
|
||||||
|
pr.author.resourcePath,
|
||||||
|
pr.author.login,
|
||||||
|
pr.author.avatarUrl,
|
||||||
|
pr.author.url,
|
||||||
|
assignableUsers.has(pr.author.login) ? 1 : 0,
|
||||||
|
);
|
||||||
|
insertPR.run(
|
||||||
|
pr.id,
|
||||||
|
pr.title,
|
||||||
|
pr.body,
|
||||||
|
pr.url,
|
||||||
|
pr.createdAt,
|
||||||
|
pr.updatedAt,
|
||||||
|
pr.closedAt,
|
||||||
|
pr.mergedAt,
|
||||||
|
pr.author.resourcePath,
|
||||||
|
);
|
||||||
|
|
||||||
for (const label of pr.labels.nodes) {
|
for (const label of pr.labels.nodes) {
|
||||||
insertLabel.run(label.id, label.name, label.color, label.description);
|
insertLabel.run(label.id, label.name, label.color, label.description);
|
||||||
insertPullRequestLabel.run(pr.id, label.id);
|
insertPullRequestLabel.run(pr.id, label.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
if(issue.author == null) { continue; }
|
if (issue.author == null) {
|
||||||
insertUser.run(issue.author.resourcePath, issue.author.login, issue.author.avatarUrl, issue.author.url, assignableUsers.has(issue.author.login) ? 1 : 0);
|
continue;
|
||||||
|
}
|
||||||
|
insertUser.run(
|
||||||
|
issue.author.resourcePath,
|
||||||
|
issue.author.login,
|
||||||
|
issue.author.avatarUrl,
|
||||||
|
issue.author.url,
|
||||||
|
assignableUsers.has(issue.author.login) ? 1 : 0,
|
||||||
|
);
|
||||||
|
|
||||||
insertIssue.run(issue.id, issue.title, issue.body, issue.url, issue.createdAt, issue.updatedAt, issue.closedAt, issue.author.resourcePath);
|
insertIssue.run(
|
||||||
|
issue.id,
|
||||||
|
issue.title,
|
||||||
|
issue.body,
|
||||||
|
issue.url,
|
||||||
|
issue.createdAt,
|
||||||
|
issue.updatedAt,
|
||||||
|
issue.closedAt,
|
||||||
|
issue.author.resourcePath,
|
||||||
|
);
|
||||||
|
|
||||||
for (const label of issue.labels.nodes) {
|
for (const label of issue.labels.nodes) {
|
||||||
insertLabel.run(label.id, label.name, label.color, label.description);
|
insertLabel.run(label.id, label.name, label.color, label.description);
|
||||||
insertIssueLabel.run(issue.id, label.id);
|
insertIssueLabel.run(issue.id, label.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
return new Response("Data synced", { status: 200 });
|
return new Response('Data synced', { status: 200 });
|
||||||
};
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Image from 'next/image';
|
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
|
||||||
import AvatarGrid from '@/app/components/AvatarGrid';
|
import AvatarGrid from '@/app/components/AvatarGrid';
|
||||||
|
|
||||||
interface Contributor {
|
interface Contributor {
|
||||||
@ -9,11 +9,11 @@ interface Contributor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Contributors = async () => {
|
const Contributors = async () => {
|
||||||
|
|
||||||
|
|
||||||
const db = new Database('db.sqlite', { readonly: true });
|
const db = new Database('db.sqlite', { readonly: true });
|
||||||
|
|
||||||
const contributors = db.prepare(`SELECT
|
const contributors = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT
|
||||||
u.login,
|
u.login,
|
||||||
u.avatarUrl,
|
u.avatarUrl,
|
||||||
COUNT(pr.id) AS pullRequestCount
|
COUNT(pr.id) AS pullRequestCount
|
||||||
@ -25,9 +25,10 @@ const Contributors = async () => {
|
|||||||
u.id
|
u.id
|
||||||
ORDER BY
|
ORDER BY
|
||||||
pullRequestCount DESC;
|
pullRequestCount DESC;
|
||||||
`).all() as Contributor[];
|
`,
|
||||||
|
)
|
||||||
|
.all() as Contributor[];
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
||||||
import { GraphiQL } from 'graphiql';
|
import { GraphiQL } from 'graphiql';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
import 'graphiql/graphiql.css';
|
import 'graphiql/graphiql.css';
|
||||||
|
|
||||||
// Create a named function for your component
|
// Create a named function for your component
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CacheProvider } from '@emotion/react';
|
|
||||||
import createCache from '@emotion/cache';
|
|
||||||
import { useServerInsertedHTML } from 'next/navigation';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import createCache from '@emotion/cache';
|
||||||
|
import { CacheProvider } from '@emotion/react';
|
||||||
|
import { useServerInsertedHTML } from 'next/navigation';
|
||||||
|
|
||||||
export default function RootStyleRegistry({ children }) {
|
export default function RootStyleRegistry({ children }) {
|
||||||
const [{ cache, flush }] = useState(() => {
|
const [{ cache, flush }] = useState(() => {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { compileMDX } from 'next-mdx-remote/rsc';
|
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import gfm from 'remark-gfm';
|
|
||||||
import rehypeToc from '@jsdevtools/rehype-toc';
|
import rehypeToc from '@jsdevtools/rehype-toc';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { compileMDX } from 'next-mdx-remote/rsc';
|
||||||
|
import path from 'path';
|
||||||
import rehypeSlug from 'rehype-slug';
|
import rehypeSlug from 'rehype-slug';
|
||||||
|
import gfm from 'remark-gfm';
|
||||||
|
|
||||||
interface ItemInfo {
|
interface ItemInfo {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import type { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { Gabarito } from 'next/font/google';
|
import { Gabarito } from 'next/font/google';
|
||||||
import EmotionRootStyleRegistry from './emotion-root-style-registry';
|
|
||||||
import { HeaderDesktop } from './components/HeaderDesktop';
|
|
||||||
import { FooterDesktop } from './components/FooterDesktop';
|
|
||||||
import { HeaderMobile } from '@/app/components/HeaderMobile';
|
import { HeaderMobile } from '@/app/components/HeaderMobile';
|
||||||
|
|
||||||
|
import { FooterDesktop } from './components/FooterDesktop';
|
||||||
|
import { HeaderDesktop } from './components/HeaderDesktop';
|
||||||
|
import EmotionRootStyleRegistry from './emotion-root-style-registry';
|
||||||
|
|
||||||
import './layout.css';
|
import './layout.css';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
import { Metadata } from 'next';
|
||||||
import { compileMDX } from 'next-mdx-remote/rsc';
|
import { compileMDX } from 'next-mdx-remote/rsc';
|
||||||
import gfm from 'remark-gfm';
|
|
||||||
import { ContentContainer } from '../components/ContentContainer';
|
|
||||||
import remarkBehead from 'remark-behead';
|
import remarkBehead from 'remark-behead';
|
||||||
import type { Metadata } from 'next';
|
import gfm from 'remark-gfm';
|
||||||
|
|
||||||
|
import { ContentContainer } from '../components/ContentContainer';
|
||||||
|
|
||||||
interface Release {
|
interface Release {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { ContentContainer } from '@/app/components/ContentContainer';
|
|
||||||
import { getPosts, Directory, FileContent } from '@/app/get-posts';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import * as TablerIcons from '@tabler/icons-react';
|
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
|
import * as TablerIcons from '@tabler/icons-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { ContentContainer } from '@/app/components/ContentContainer';
|
||||||
|
import { Directory, FileContent, getPosts } from '@/app/get-posts';
|
||||||
|
|
||||||
function loadIcon(iconName?: string) {
|
function loadIcon(iconName?: string) {
|
||||||
const name = iconName ? iconName : 'IconCategory';
|
const name = iconName ? iconName : 'IconCategory';
|
||||||
|
|||||||
Reference in New Issue
Block a user