From eb5bc770e800e47f554510c59a746db2fc4e9a91 Mon Sep 17 00:00:00 2001 From: geisterfurz007 Date: Sat, 9 Dec 2023 14:16:24 +0100 Subject: [PATCH] chore: upgrade to atmina/linting --- .github/workflows/lint-and-test.yml | 12 +- package.json | 11 +- packages/cms/eslint.config.js | 7 + packages/cms/package.json | 14 +- packages/cms/src/collections/groupchats.ts | 58 +-- packages/cms/src/collections/users.ts | 6 +- packages/cms/src/cron-jobs/groupchat-sync.ts | 54 +-- packages/cms/src/cron-jobs/index.ts | 2 +- packages/cms/src/dataseeder/main.ts | 14 +- .../cms/src/dataseeder/seed-groupchats.ts | 34 +- .../cms/src/dataseeder/seed-typesense-key.ts | 14 +- packages/cms/src/dataseeder/seed-users.ts | 10 +- packages/cms/src/dataseeder/upsert.ts | 22 +- .../queries/search-token-by-authenticated.ts | 26 +- packages/cms/src/init-payload.ts | 12 +- packages/cms/src/lib/typesense.ts | 6 +- .../cms/src/migrations/20231202_131425.ts | 11 +- packages/cms/src/payload-types.ts | 10 +- packages/cms/src/payload.config.ts | 28 +- packages/cms/src/server.ts | 18 +- packages/cms/src/utils/ensure-db-exists.ts | 6 +- packages/cms/src/utils/merge-queries.ts | 4 +- packages/common/.eslintrc.json | 11 - packages/common/README.md | 1 - packages/common/package.json | 14 - packages/common/tsconfig.json | 71 --- packages/eslint-config/.eslintrc.json | 47 -- packages/eslint-config/index.js | 2 - packages/eslint-config/package.json | 16 - packages/server/.eslintrc.json | 17 - packages/server/Dockerfile | 1 - packages/server/environment.d.ts | 2 +- packages/server/eslint.config.js | 25 ++ packages/server/package.json | 19 +- packages/server/src/config/grant.ts | 8 +- packages/server/src/config/index.ts | 4 +- packages/server/src/config/session.ts | 16 +- .../server/src/features/auth/auth-service.ts | 48 +-- .../src/features/auth/discord-callback.ts | 26 +- .../src/features/auth/graphql-auth-checker.ts | 10 +- packages/server/src/features/auth/index.ts | 10 +- .../src/features/auth/mutations/logout.ts | 26 +- .../buddy-project/buddy-project-status.ts | 14 +- .../cron-jobs/ghost-check-cron.ts | 26 +- .../buddy-project/cron-jobs/matching-cron.ts | 34 +- .../features/buddy-project/cron-jobs/texts.ts | 2 +- .../mutations/block-mutations.ts | 8 +- .../mutations/ghost-mutations.ts | 24 +- .../mutations/set-matching-enabled.ts | 8 +- .../mutations/sign-out-yesbot.ts | 8 +- .../buddy-project/mutations/sign-up-web.ts | 50 ++- .../buddy-project/mutations/sign-up-yesbot.ts | 28 +- .../queries/buddy-project-by-user-id.ts | 10 +- .../queries/buddy-project-entry-username.ts | 16 +- .../queries/buddy-project-status-query.ts | 18 +- .../buddy-project/services/block-service.ts | 22 +- .../services/buddy-project.service.ts | 16 +- .../buddy-project/services/ghost-service.ts | 44 +- .../buddy-project/services/match-service.ts | 18 +- packages/server/src/features/discord/index.ts | 18 +- packages/server/src/features/index.ts | 6 +- .../src/features/user/authenticated-user.ts | 4 +- packages/server/src/features/user/index.ts | 2 +- .../server/src/features/user/queries/me.ts | 22 +- .../user/queries/user-server-state.ts | 14 +- packages/server/src/graphql-directives.ts | 4 +- packages/server/src/index.ts | 28 +- packages/server/src/servers/index.ts | 2 +- packages/server/src/servers/public.ts | 68 +-- packages/server/src/servers/yesbot-schema.ts | 26 +- packages/server/src/servers/yesbot.ts | 28 +- .../services/cron/cron-start-side-effect.ts | 6 +- .../server/src/services/key-value-store.ts | 12 +- .../src/services/logging/log-service.ts | 8 +- packages/server/src/services/logging/log.ts | 13 +- .../services/resolvers/resolver-directive.ts | 47 +- packages/server/src/types/env.d.ts | 2 +- packages/server/src/types/index.ts | 6 +- packages/server/tsconfig.json | 1 + packages/web/.eslintrc.json | 1 - packages/web/.prettierignore | 25 ++ packages/web/.storybook/main.ts | 18 +- packages/web/.storybook/preview.ts | 8 +- packages/web/Dockerfile | 1 - packages/web/assets/index.ts | 10 +- packages/web/codegen.ts | 28 +- packages/web/cypress.config.ts | 6 +- packages/web/cypress/e2e/example.cy.ts | 10 +- packages/web/cypress/support/e2e.ts | 2 +- packages/web/declaration.d.ts | 4 +- packages/web/environment.d.ts | 2 +- packages/web/eslint.config.js | 15 + packages/web/graphql.config.yml | 14 +- packages/web/next.config.js | 22 +- packages/web/package.json | 17 +- packages/web/src/__generated__/graphql.ts | 85 ++-- .../actions/signup-server-action.ts | 4 +- .../components/buddy-project-button.tsx | 30 +- .../components/server-join-confirmation.tsx | 12 +- .../components/signup-success-modal.tsx | 18 +- packages/web/src/app/buddyproject/page.tsx | 40 +- .../components/group-chat-search.tsx | 62 ++- .../components/use-groupchat-search.ts | 36 +- packages/web/src/app/groupchats/page.tsx | 18 +- packages/web/src/app/layout.tsx | 58 +-- packages/web/src/app/legal/imprint/page.tsx | 34 +- packages/web/src/app/legal/layout.tsx | 6 +- packages/web/src/app/legal/privacy/page.tsx | 143 +++--- packages/web/src/app/nav.tsx | 26 +- packages/web/src/app/not-found.tsx | 8 +- packages/web/src/app/page.tsx | 2 +- packages/web/src/app/providers.tsx | 12 +- .../cookie-consent/cookie-consent-modal.tsx | 36 +- .../cookie-consent/cookie-consent.tsx | 13 +- .../components/scrollbar-width-provider.tsx | 8 +- packages/web/src/context/typesense/client.ts | 14 +- packages/web/src/context/typesense/index.ts | 4 +- .../web/src/context/typesense/provider.tsx | 18 +- .../src/context/user/logout-server-action.ts | 10 +- .../web/src/context/user/navigate-to-login.ts | 2 +- .../web/src/context/user/user-context.tsx | 10 +- packages/web/src/context/user/user.tsx | 4 +- packages/web/src/lib/graphql/client.ts | 22 +- .../web/src/lib/hooks/use-debounced-value.ts | 2 +- packages/web/src/ui/buddyproject/common.tsx | 16 +- packages/web/src/ui/buddyproject/index.ts | 2 +- .../info-grid/info-grid.stories.tsx | 14 +- .../ui/buddyproject/info-grid/info-grid.tsx | 40 +- .../steps/chatting-with-your-buddy.tsx | 6 +- .../web/src/ui/buddyproject/steps/index.ts | 10 +- .../web/src/ui/buddyproject/steps/join.tsx | 16 +- .../web/src/ui/buddyproject/steps/matched.tsx | 16 +- .../steps/signed-up-confirmation.tsx | 8 +- .../ui/buddyproject/steps/steps.stories.tsx | 12 +- .../ui/buddyproject/steps/wait-for-match.tsx | 6 +- packages/web/src/ui/client.ts | 8 +- .../src/ui/common/button/button.stories.tsx | 12 +- packages/web/src/ui/common/button/button.tsx | 35 +- .../collapsible/collapsible.stories.tsx | 12 +- .../src/ui/common/collapsible/collapsible.tsx | 22 +- .../components/affiliation-exclusion.tsx | 4 +- .../ui/common/footer/components/copyright.tsx | 4 +- .../footer/components/footer-link-row.tsx | 16 +- .../src/ui/common/footer/footer.stories.tsx | 14 +- packages/web/src/ui/common/footer/footer.tsx | 18 +- .../src/ui/common/heading/heading.stories.tsx | 12 +- .../web/src/ui/common/heading/heading.tsx | 34 +- .../common/icons/default-discord-avatar.tsx | 8 +- .../web/src/ui/common/icons/discord-logo.tsx | 10 +- .../web/src/ui/common/image/image.stories.tsx | 22 +- packages/web/src/ui/common/image/image.tsx | 20 +- .../web/src/ui/common/link/link.stories.tsx | 14 +- packages/web/src/ui/common/link/link.tsx | 20 +- .../web/src/ui/common/logo/logo.stories.tsx | 22 +- packages/web/src/ui/common/logo/logo.tsx | 36 +- .../web/src/ui/common/modal/modal.stories.tsx | 16 +- packages/web/src/ui/common/modal/modal.tsx | 36 +- .../web/src/ui/common/navigation/index.ts | 4 +- .../login-button/login-button.stories.tsx | 12 +- .../navigation/login-button/login-button.tsx | 18 +- .../navigation/nav-link/nav-link.stories.tsx | 20 +- .../common/navigation/nav-link/nav-link.tsx | 22 +- .../ui/common/navigation/navigation.types.ts | 2 +- .../navigations/desktop-navigation.tsx | 22 +- .../navigations/joined-navigation.tsx | 10 +- .../navigations/mobile-navigation.tsx | 68 +-- .../navigations/navigation.stories.tsx | 24 +- .../navigation/profile/profile.stories.tsx | 20 +- .../ui/common/navigation/profile/profile.tsx | 47 +- .../scroll-to-action-container.stories.tsx | 28 +- .../scroll-to-action-container.tsx | 22 +- .../toggle-button/toggle-button.stories.tsx | 16 +- .../ui/common/toggle-button/toggle-button.tsx | 18 +- packages/web/src/ui/groupchats/client.ts | 4 +- .../group-chat-result.module.css | 14 +- .../group-chat-result.stories.ts | 18 +- .../group-chat-result/group-chat-result.tsx | 64 ++- .../group-chat-search-bar.stories.tsx | 14 +- .../group-chat-search-bar.tsx | 34 +- .../group-chat-search-bar/platform-filter.tsx | 32 +- .../group-chat-search-bar/search-input.tsx | 24 +- packages/web/src/ui/groupchats/index.ts | 2 +- packages/web/src/ui/index.ts | 14 +- packages/web/src/ui/misc/back-link.tsx | 8 +- packages/web/src/ui/misc/client.ts | 4 +- packages/web/src/ui/misc/index.tsx | 2 +- packages/web/src/ui/misc/wip/wip.stories.tsx | 10 +- packages/web/src/ui/misc/wip/wip.tsx | 26 +- packages/web/src/ui/variants.ts | 4 +- packages/web/styles/globals.css | 4 +- packages/web/tailwind.config.js | 72 ++-- yarn.lock | 407 ++++++++++-------- 192 files changed, 1957 insertions(+), 2030 deletions(-) create mode 100644 packages/cms/eslint.config.js delete mode 100644 packages/common/.eslintrc.json delete mode 100644 packages/common/README.md delete mode 100644 packages/common/package.json delete mode 100644 packages/common/tsconfig.json delete mode 100644 packages/eslint-config/.eslintrc.json delete mode 100644 packages/eslint-config/index.js delete mode 100644 packages/eslint-config/package.json delete mode 100644 packages/server/.eslintrc.json create mode 100644 packages/server/eslint.config.js create mode 100644 packages/web/.prettierignore create mode 100644 packages/web/eslint.config.js diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 43facc46..09408c0e 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -20,7 +20,7 @@ jobs: - uses: ./.github/actions/yarn-action name: "Install and check formatting" with: - command: format:check + command: prettier:check test: name: "Test" @@ -51,3 +51,13 @@ jobs: name: "Install and tsc server" with: command: workspace @yestheory.family/server tsc --noEmit + + tsc-cms: + name: "TSC CMS" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/yarn-action + name: "Install and tsc cms" + with: + command: workspace @yestheory.family/cms tsc --noEmit diff --git a/package.json b/package.json index 99f71b5b..f3d37c06 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "scripts": { "e2e:open": "yarn workspace @yestheory.family/web e2e:open", "e2e:run": "yarn workspace @yestheory.family/web e2e:run", - "eslint:check": "yarn workspaces foreach --all run lint", - "eslint:write": "yarn workspaces foreach --all run lint --fix", - "format:check": "prettier --check packages", - "format:write": "prettier --write packages", + "eslint:check": "yarn workspaces foreach --all run eslint:check", + "eslint:fix": "yarn workspaces foreach --all run eslint:fix", + "prettier:check": "yarn workspaces foreach --all run prettier:check", + "prettier:fix": "yarn workspaces foreach --all run prettier:fix", "server:dev": "yarn workspace @yestheory.family/server start", "server:generate": "yarn workspace @yestheory.family/server generate", "storybook": "yarn workspace @yestheory.family/web storybook", @@ -18,9 +18,6 @@ "workspaces": [ "packages/*" ], - "devDependencies": { - "prettier": "3.1.0" - }, "resolutions": { "react-test-renderer": "18.2.0" }, diff --git a/packages/cms/eslint.config.js b/packages/cms/eslint.config.js new file mode 100644 index 00000000..bf73a3bd --- /dev/null +++ b/packages/cms/eslint.config.js @@ -0,0 +1,7 @@ +/** + * @type {import('eslint').Linter.FlatConfig[]} + */ +module.exports = [ + ...require('@atmina/linting/eslint/recommended'), + require('@atmina/linting/eslint/react'), +]; diff --git a/packages/cms/package.json b/packages/cms/package.json index 43dc6ae4..cd498f43 100644 --- a/packages/cms/package.json +++ b/packages/cms/package.json @@ -13,7 +13,12 @@ "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/", "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types", - "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema" + "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema", + "lint": "yarn run lint:fix && yarn run prettier:fix", + "eslint:check": "eslint . --report-unused-disable-directives --max-warnings 0", + "eslint:fix": "eslint . --fix --report-unused-disable-directives --max-warnings 0", + "prettier:check": "prettier . --check", + "prettier:fix": "prettier . --write" }, "dependencies": { "@payloadcms/bundler-vite": "^0.1.5", @@ -26,13 +31,18 @@ "typesense": "^1.7.2" }, "devDependencies": { + "@atmina/linting": "^2.0.1", + "@types/eslint": "^8.44.8", "@types/express": "^4.17.21", "@types/node-cron": "^3.0.11", "@types/pg": "^8.10.9", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", + "eslint": "^8.55.0", + "prettier": "^3.1.0", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "typescript": "^5.3.2" - } + }, + "prettier": "@atmina/linting/prettier" } diff --git a/packages/cms/src/collections/groupchats.ts b/packages/cms/src/collections/groupchats.ts index 45362360..9630f603 100644 --- a/packages/cms/src/collections/groupchats.ts +++ b/packages/cms/src/collections/groupchats.ts @@ -1,42 +1,42 @@ -import { type CollectionConfig } from "payload/types"; -import { typesenseClient } from "../lib/typesense"; +import {type CollectionConfig} from 'payload/types'; +import {typesenseClient} from '../lib/typesense'; export const Groupchats: CollectionConfig = { - slug: "groupchats", + slug: 'groupchats', access: { read: () => true, }, admin: { - useAsTitle: "name", - defaultColumns: ["name", "url", "platform"], + useAsTitle: 'name', + defaultColumns: ['name', 'url', 'platform'], }, fields: [ { - name: "name", - type: "text", + name: 'name', + type: 'text', unique: true, required: true, }, { - name: "platform", - type: "select", + name: 'platform', + type: 'select', required: true, options: [ - { label: "Discord", value: "discord" }, - { label: "Facebook", value: "facebook" }, - { label: "Signal", value: "signal" }, - { label: "Telegram", value: "telegram" }, - { label: "WhatsApp", value: "whatsapp" }, + {label: 'Discord', value: 'discord'}, + {label: 'Facebook', value: 'facebook'}, + {label: 'Signal', value: 'signal'}, + {label: 'Telegram', value: 'telegram'}, + {label: 'WhatsApp', value: 'whatsapp'}, ], }, - { name: "description", type: "text" }, + {name: 'description', type: 'text'}, { - name: "url", - type: "text", + name: 'url', + type: 'text', required: true, hooks: { beforeValidate: [ - ({ value }) => { + ({value}) => { if (value && !value.match(/^https?:\/\//i)) { return `https://${value}`; } @@ -47,28 +47,28 @@ export const Groupchats: CollectionConfig = { }, }, { - name: "keywords", - type: "array", + name: 'keywords', + type: 'array', required: true, - fields: [{ name: "value", type: "text", required: true }], + fields: [{name: 'value', type: 'text', required: true}], }, { - name: "promoted", - type: "number", + name: 'promoted', + type: 'number', min: 0, max: 100, required: true, defaultValue: 0, admin: { description: - "This value may be used to push results. A value of 0 means no promotion. Any value between 1 and 100 may be used to order promoted groupchats.", + 'This value may be used to push results. A value of 0 means no promotion. Any value between 1 and 100 may be used to order promoted groupchats.', }, }, ], hooks: { afterChange: [ - async ({ doc, context }) => { - if ("dataseeder" in context && context.dataseeder) return; + async ({doc, context}) => { + if ('dataseeder' in context && context.dataseeder) return; const typesenseDoc = { ...doc, @@ -76,15 +76,15 @@ export const Groupchats: CollectionConfig = { keywords: doc.keywords.map((k) => k.value), }; await typesenseClient - .collections("groupchats") + .collections('groupchats') .documents() .upsert(typesenseDoc); }, ], afterDelete: [ - async ({ id }) => { + async ({id}) => { await typesenseClient - .collections("groupchats") + .collections('groupchats') .documents() .delete(id.toString()); }, diff --git a/packages/cms/src/collections/users.ts b/packages/cms/src/collections/users.ts index 066cdc32..d1c4b38e 100644 --- a/packages/cms/src/collections/users.ts +++ b/packages/cms/src/collections/users.ts @@ -1,10 +1,10 @@ -import { CollectionConfig } from "payload/types"; +import {type CollectionConfig} from 'payload/types'; export const Users: CollectionConfig = { - slug: "users", + slug: 'users', auth: true, admin: { - useAsTitle: "email", + useAsTitle: 'email', }, fields: [], }; diff --git a/packages/cms/src/cron-jobs/groupchat-sync.ts b/packages/cms/src/cron-jobs/groupchat-sync.ts index cc649c1b..836e768a 100644 --- a/packages/cms/src/cron-jobs/groupchat-sync.ts +++ b/packages/cms/src/cron-jobs/groupchat-sync.ts @@ -1,47 +1,47 @@ -import payload from "payload"; -import { schedule } from "node-cron"; -import { typesenseClient } from "../lib/typesense"; -import { CollectionCreateSchema } from "typesense/lib/Typesense/Collections"; +import {schedule} from 'node-cron'; +import payload from 'payload'; +import {type CollectionCreateSchema} from 'typesense/lib/Typesense/Collections'; +import {typesenseClient} from '../lib/typesense'; // TODO see if we can remove this alltogether thanks to the hook export const setupGroupchatSync = async () => { - console.info("Setting up groupchats!"); + console.info('Setting up groupchats!'); await ensureTypesenseCollectionExists(); - const everyHour = "0 * * * *"; - schedule(everyHour, syncGroupchatsToTypesense, { runOnInit: true }); + const everyHour = '0 * * * *'; + schedule(everyHour, syncGroupchatsToTypesense, {runOnInit: true}); }; -const collectionName = "groupchats"; +const collectionName = 'groupchats'; const schema: CollectionCreateSchema = { name: collectionName, fields: [ { - name: "name", - type: "string", + name: 'name', + type: 'string', }, { - name: "keywords", - type: "string[]", + name: 'keywords', + type: 'string[]', optional: true, }, { - name: "url", - type: "string", + name: 'url', + type: 'string', }, { - name: "description", - type: "string", + name: 'description', + type: 'string', optional: true, }, { - name: "platform", - type: "string", + name: 'platform', + type: 'string', facet: true, }, { - name: "promoted", - type: "int32", + name: 'promoted', + type: 'int32', }, ], }; @@ -59,35 +59,35 @@ const ensureTypesenseCollectionExists = async () => { } await typesenseClient.collections().create(schema); - console.info("Schema is up to date in Typesense"); + console.info('Schema is up to date in Typesense'); }; const syncGroupchatsToTypesense = async () => { - console.info("Syncing groupchats to Typesense"); + console.info('Syncing groupchats to Typesense'); try { - const { docs: groupchats } = await payload.find({ + const {docs: groupchats} = await payload.find({ collection: collectionName, pagination: false, }); const typesenseChats = groupchats.map( - ({ id, createdAt, updatedAt, keywords, ...rest }) => ({ + ({id, createdAt, updatedAt, keywords, ...rest}) => ({ ...rest, id: id.toString(), - keywords: keywords?.map(({ value }) => value) ?? [], + keywords: keywords?.map(({value}) => value) ?? [], }), ); if (typesenseChats.length === 0) { - console.info("No groupchats available, skipping import"); + console.info('No groupchats available, skipping import'); return; } await typesenseClient .collections(collectionName) .documents() - .import(typesenseChats, { action: "upsert" }); + .import(typesenseChats, {action: 'upsert'}); console.info(`Synced ${groupchats.length} groupchats to Typesense`); } catch (e) { diff --git a/packages/cms/src/cron-jobs/index.ts b/packages/cms/src/cron-jobs/index.ts index e4afa33c..f3d103d5 100644 --- a/packages/cms/src/cron-jobs/index.ts +++ b/packages/cms/src/cron-jobs/index.ts @@ -1,4 +1,4 @@ -import { setupGroupchatSync } from "./groupchat-sync"; +import {setupGroupchatSync} from './groupchat-sync'; export const setupCronJobs = async () => { await Promise.all([setupGroupchatSync()]); diff --git a/packages/cms/src/dataseeder/main.ts b/packages/cms/src/dataseeder/main.ts index 692a5a6f..c819173d 100644 --- a/packages/cms/src/dataseeder/main.ts +++ b/packages/cms/src/dataseeder/main.ts @@ -1,10 +1,10 @@ -import path from "node:path"; +import path from 'node:path'; -import { initPayload } from "../init-payload"; -import { seedUsers } from "./seed-users"; -import { seedGroupchats } from "./seed-groupchats"; -import { seedTypesenseKey } from "./seed-typesense-key"; -import { typesenseReady } from "../lib/typesense"; +import {initPayload} from '../init-payload'; +import {typesenseReady} from '../lib/typesense'; +import {seedGroupchats} from './seed-groupchats'; +import {seedTypesenseKey} from './seed-typesense-key'; +import {seedUsers} from './seed-users'; export const main = async () => { process.env.PAYLOAD_CONFIG_PATH = path.resolve( @@ -12,7 +12,7 @@ export const main = async () => { `../payload.config${path.extname(__filename)}`, ); - await initPayload({ local: true }); + await initPayload({local: true}); await seedUsers(); await seedGroupchats(); diff --git a/packages/cms/src/dataseeder/seed-groupchats.ts b/packages/cms/src/dataseeder/seed-groupchats.ts index 9ed59309..80fc515f 100644 --- a/packages/cms/src/dataseeder/seed-groupchats.ts +++ b/packages/cms/src/dataseeder/seed-groupchats.ts @@ -1,33 +1,29 @@ -import { upsert } from "./upsert"; +import {upsert} from './upsert'; export const seedGroupchats = async () => { await upsert({ - collection: "groupchats", - key: "name", + collection: 'groupchats', + key: 'name', data: { - name: "Yes Famburg", - description: "Hamburgs YesFam group on WhatsApp", - platform: "whatsapp", - url: "https://example.com", - keywords: [ - { value: "Hamburg" }, - { value: "Germany" }, - { value: "Europe" }, - ], + name: 'Yes Famburg', + description: 'Hamburgs YesFam group on WhatsApp', + platform: 'whatsapp', + url: 'https://example.com', + keywords: [{value: 'Hamburg'}, {value: 'Germany'}, {value: 'Europe'}], promoted: 0, }, }); await upsert({ - collection: "groupchats", - key: "name", + collection: 'groupchats', + key: 'name', data: { - name: "Yes Theory Fam Discord", + name: 'Yes Theory Fam Discord', description: - "The semi-official community run Discord server of the Yes Fam", - platform: "discord", - url: "https://discord.gg/yestheory", - keywords: [{ value: "Global" }], + 'The semi-official community run Discord server of the Yes Fam', + platform: 'discord', + url: 'https://discord.gg/yestheory', + keywords: [{value: 'Global'}], promoted: 100, }, }); diff --git a/packages/cms/src/dataseeder/seed-typesense-key.ts b/packages/cms/src/dataseeder/seed-typesense-key.ts index ccbb095f..87b55b93 100644 --- a/packages/cms/src/dataseeder/seed-typesense-key.ts +++ b/packages/cms/src/dataseeder/seed-typesense-key.ts @@ -1,17 +1,17 @@ -import { typesenseClient } from "../lib/typesense"; +import {typesenseClient} from '../lib/typesense'; -export const apiKey = "yestheory-family-typesense-search-key"; +export const apiKey = 'yestheory-family-typesense-search-key'; export const seedTypesenseKey = async () => { try { await typesenseClient.keys().create({ - collections: ["*"], - actions: ["documents:search"], + collections: ['*'], + actions: ['documents:search'], value: apiKey, - description: "Default global search key", + description: 'Default global search key', }); - console.info("Search API Key created!"); + console.info('Search API Key created!'); } catch { - console.info("Search API Key already exists!"); + console.info('Search API Key already exists!'); } }; diff --git a/packages/cms/src/dataseeder/seed-users.ts b/packages/cms/src/dataseeder/seed-users.ts index fb7b10be..d403b420 100644 --- a/packages/cms/src/dataseeder/seed-users.ts +++ b/packages/cms/src/dataseeder/seed-users.ts @@ -1,12 +1,12 @@ -import { upsert } from "./upsert"; +import {upsert} from './upsert'; export const seedUsers = async () => { await upsert({ - key: "email", - collection: "users", + key: 'email', + collection: 'users', data: { - email: process.env.INITIAL_ADMIN_MAIL ?? "admin@example.com", - password: process.env.INITIAL_ADMIN_PASSWORD ?? "123456", + email: process.env.INITIAL_ADMIN_MAIL ?? 'admin@example.com', + password: process.env.INITIAL_ADMIN_PASSWORD ?? '123456', }, }); }; diff --git a/packages/cms/src/dataseeder/upsert.ts b/packages/cms/src/dataseeder/upsert.ts index 6b084a3f..d8e6042b 100644 --- a/packages/cms/src/dataseeder/upsert.ts +++ b/packages/cms/src/dataseeder/upsert.ts @@ -1,6 +1,6 @@ -import payload, { GeneratedTypes } from "payload"; +import payload, {type GeneratedTypes} from 'payload'; -type CollectionKey = keyof GeneratedTypes["collections"]; +type CollectionKey = keyof GeneratedTypes['collections']; type CreateOptions = Parameters< typeof payload.create >[0]; @@ -9,41 +9,41 @@ type UpdateOptions = Parameters< >[0]; type CollectionField = - keyof GeneratedTypes["collections"][T]; + keyof GeneratedTypes['collections'][T]; type UpsertArgs = { collection: T; - data: Partial; + data: Partial; key: CollectionField | CollectionField[]; -} & Omit & UpdateOptions, "id" | "where">; +} & Omit & UpdateOptions, 'id' | 'where'>; export const upsert = async ( optionsAndKey: UpsertArgs, ) => { - const { key, ...options } = optionsAndKey; + const {key, ...options} = optionsAndKey; const firstKey: CollectionField = Array.isArray(key) ? key[0] : key; const keyValue = options.data[firstKey]; const collection = options.collection; const where = { and: (Array.isArray(key) ? key : [key]).map((k) => ({ - [k]: { equals: options.data[k] }, + [k]: {equals: options.data[k]}, })), }; - const { totalDocs, docs } = await payload.find({ + const {totalDocs, docs} = await payload.find({ collection, where, limit: 2, depth: 0, }); - if (totalDocs > 1) throw new Error("Key is not unique"); + if (totalDocs > 1) throw new Error('Key is not unique'); if (totalDocs === 1) { const id = docs[0].id; - await payload.update({ id, ...options, context: { dataseeder: true } }); + await payload.update({id, ...options, context: {dataseeder: true}}); console.info(`${collection}: Updated ${keyValue}`); } else { - await payload.create({ ...options, context: { dataseeder: true } }); + await payload.create({...options, context: {dataseeder: true}}); console.info(`${collection}: Created ${keyValue}`); } }; diff --git a/packages/cms/src/graphql/queries/search-token-by-authenticated.ts b/packages/cms/src/graphql/queries/search-token-by-authenticated.ts index 622c0ee2..d780b0ce 100644 --- a/packages/cms/src/graphql/queries/search-token-by-authenticated.ts +++ b/packages/cms/src/graphql/queries/search-token-by-authenticated.ts @@ -1,28 +1,28 @@ -import { QueryFactory } from "../../utils/merge-queries"; -import { typesenseClient } from "../../lib/typesense"; -import { apiKey } from "../../dataseeder/seed-typesense-key"; -import { GeneratedTypes } from "payload"; +import {type GeneratedTypes} from 'payload'; +import {apiKey} from '../../dataseeder/seed-typesense-key'; +import {typesenseClient} from '../../lib/typesense'; +import {type QueryFactory} from '../../utils/merge-queries'; type GroupchatPlatform = - GeneratedTypes["collections"]["groupchats"]["platform"]; + GeneratedTypes['collections']['groupchats']['platform']; export const searchTokenByAuthenticatedQuery: QueryFactory< string, - { isAuthenticated: boolean } -> = (GraphQL, payload) => ({ + {isAuthenticated: boolean} +> = (GraphQL) => ({ type: GraphQL.GraphQLString, - args: { isAuthenticated: { type: GraphQL.GraphQLBoolean } }, - resolve: async (_: unknown, { isAuthenticated }) => { - const accessiblePlatforms: GroupchatPlatform[] = ["facebook"]; + args: {isAuthenticated: {type: GraphQL.GraphQLBoolean}}, + resolve: async (_: unknown, {isAuthenticated}) => { + const accessiblePlatforms: GroupchatPlatform[] = ['facebook']; if (isAuthenticated) { - accessiblePlatforms.push("discord", "signal", "telegram", "whatsapp"); + accessiblePlatforms.push('discord', 'signal', 'telegram', 'whatsapp'); } - const filterBy = `platform:=[${accessiblePlatforms.join(",")}]`; + const filterBy = `platform:=[${accessiblePlatforms.join(',')}]`; return typesenseClient .keys() - .generateScopedSearchKey(apiKey, { filter_by: filterBy }); + .generateScopedSearchKey(apiKey, {filter_by: filterBy}); }, }); diff --git a/packages/cms/src/init-payload.ts b/packages/cms/src/init-payload.ts index 320a43cf..0d63d1b1 100644 --- a/packages/cms/src/init-payload.ts +++ b/packages/cms/src/init-payload.ts @@ -1,15 +1,15 @@ -import { config } from "dotenv"; -import payload from "payload"; -import { type InitOptions } from "payload/config"; -import { ensureDbExists } from "./utils/ensure-db-exists"; +import {config} from 'dotenv'; +import payload from 'payload'; +import {type InitOptions} from 'payload/config'; +import {ensureDbExists} from './utils/ensure-db-exists'; export const initPayload = async (additionalOptions?: Partial) => { config(); - console.info("Ensuring database exists"); + console.info('Ensuring database exists'); await ensureDbExists(); - if (!process.env.PAYLOAD_SECRET) throw new Error("Missing PAYLOAD_SECRET"); + if (!process.env.PAYLOAD_SECRET) throw new Error('Missing PAYLOAD_SECRET'); return await payload.init({ secret: process.env.PAYLOAD_SECRET, diff --git a/packages/cms/src/lib/typesense.ts b/packages/cms/src/lib/typesense.ts index abffe4b0..b240e6f1 100644 --- a/packages/cms/src/lib/typesense.ts +++ b/packages/cms/src/lib/typesense.ts @@ -1,7 +1,7 @@ -import { Client } from "typesense"; +import {Client} from 'typesense'; const apiUrl = new URL( - process.env.TYPESENSE_API_URL ?? "http://localhost:8108", + process.env.TYPESENSE_API_URL ?? 'http://localhost:8108', ); const protocol = apiUrl.protocol; @@ -14,7 +14,7 @@ export const typesenseClient = new Client({ protocol: protocol.substring(0, protocol.length - 1), }, ], - apiKey: process.env.TYPESENSE_API_KEY ?? "1234567890", + apiKey: process.env.TYPESENSE_API_KEY ?? '1234567890', connectionTimeoutSeconds: 2, }); diff --git a/packages/cms/src/migrations/20231202_131425.ts b/packages/cms/src/migrations/20231202_131425.ts index 954cb848..e9c2864e 100644 --- a/packages/cms/src/migrations/20231202_131425.ts +++ b/packages/cms/src/migrations/20231202_131425.ts @@ -1,7 +1,10 @@ -import { MigrateUpArgs, MigrateDownArgs } from "@payloadcms/db-postgres"; -import { sql } from "drizzle-orm"; +import { + type MigrateUpArgs, + type MigrateDownArgs, +} from '@payloadcms/db-postgres'; +import {sql} from 'drizzle-orm'; -export async function up({ payload }: MigrateUpArgs): Promise { +export async function up({payload}: MigrateUpArgs): Promise { await payload.db.drizzle.execute(sql` DO $$ BEGIN @@ -96,7 +99,7 @@ END $$; `); } -export async function down({ payload }: MigrateDownArgs): Promise { +export async function down({payload}: MigrateDownArgs): Promise { await payload.db.drizzle.execute(sql` DROP TABLE "users"; diff --git a/packages/cms/src/payload-types.ts b/packages/cms/src/payload-types.ts index 257f857d..77f35173 100644 --- a/packages/cms/src/payload-types.ts +++ b/packages/cms/src/payload-types.ts @@ -10,8 +10,8 @@ export interface Config { collections: { users: User; groupchats: Groupchat; - "payload-preferences": PayloadPreference; - "payload-migrations": PayloadMigration; + 'payload-preferences': PayloadPreference; + 'payload-migrations': PayloadMigration; }; globals: {}; } @@ -31,7 +31,7 @@ export interface User { export interface Groupchat { id: number; name: string; - platform: "discord" | "facebook" | "signal" | "telegram" | "whatsapp"; + platform: 'discord' | 'facebook' | 'signal' | 'telegram' | 'whatsapp'; description?: string | null; url: string; keywords: { @@ -45,7 +45,7 @@ export interface Groupchat { export interface PayloadPreference { id: number; user: { - relationTo: "users"; + relationTo: 'users'; value: number | User; }; key?: string | null; @@ -69,6 +69,6 @@ export interface PayloadMigration { createdAt: string; } -declare module "payload" { +declare module 'payload' { export interface GeneratedTypes extends Config {} } diff --git a/packages/cms/src/payload.config.ts b/packages/cms/src/payload.config.ts index cdea7060..c7ccd3b6 100644 --- a/packages/cms/src/payload.config.ts +++ b/packages/cms/src/payload.config.ts @@ -1,33 +1,33 @@ -import { buildConfig, Config } from "payload/config"; -import path from "path"; -import { viteBundler } from "@payloadcms/bundler-vite"; -import { slateEditor } from "@payloadcms/richtext-slate"; -import { postgresAdapter } from "@payloadcms/db-postgres"; -import { Users } from "./collections/users"; -import { Groupchats } from "./collections/groupchats"; -import { mergeQueries } from "./utils/merge-queries"; -import { searchTokenByAuthenticatedQuery } from "./graphql/queries/search-token-by-authenticated"; +import path from 'path'; +import {viteBundler} from '@payloadcms/bundler-vite'; +import {postgresAdapter} from '@payloadcms/db-postgres'; +import {slateEditor} from '@payloadcms/richtext-slate'; +import {buildConfig, type Config} from 'payload/config'; +import {Groupchats} from './collections/groupchats'; +import {Users} from './collections/users'; +import {searchTokenByAuthenticatedQuery} from './graphql/queries/search-token-by-authenticated'; +import {mergeQueries} from './utils/merge-queries'; const config: Config = { admin: { user: Users.slug, - buildPath: path.resolve(__dirname, "../build"), + buildPath: path.resolve(__dirname, '../build'), bundler: viteBundler(), }, editor: slateEditor({}), collections: [Users, Groupchats], db: postgresAdapter({ - migrationDir: path.resolve(__dirname, "migrations"), - pool: { connectionString: process.env.DATABASE_URI }, + migrationDir: path.resolve(__dirname, 'migrations'), + pool: {connectionString: process.env.DATABASE_URI}, }), typescript: { - outputFile: path.resolve(__dirname, "payload-types.ts"), + outputFile: path.resolve(__dirname, 'payload-types.ts'), }, graphQL: { queries: mergeQueries({ searchTokenByAuthenticated: searchTokenByAuthenticatedQuery, }), - schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"), + schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), }, }; diff --git a/packages/cms/src/server.ts b/packages/cms/src/server.ts index bb4ef75f..945db074 100644 --- a/packages/cms/src/server.ts +++ b/packages/cms/src/server.ts @@ -1,15 +1,17 @@ -import express from "express"; -import payload from "payload"; -import { initPayload } from "./init-payload"; -import { setupCronJobs } from "./cron-jobs"; -import { typesenseReady } from "./lib/typesense"; +import {config} from 'dotenv'; +import express from 'express'; +import payload from 'payload'; +import {setupCronJobs} from './cron-jobs'; +import {initPayload} from './init-payload'; +import {typesenseReady} from './lib/typesense'; + +config(); -require("dotenv").config(); const app = express(); // Redirect root to Admin panel -app.get("/", (_, res) => { - res.redirect("/admin"); +app.get('/', (_, res) => { + res.redirect('/admin'); }); const start = async () => { diff --git a/packages/cms/src/utils/ensure-db-exists.ts b/packages/cms/src/utils/ensure-db-exists.ts index 21a5e365..0f3769cb 100644 --- a/packages/cms/src/utils/ensure-db-exists.ts +++ b/packages/cms/src/utils/ensure-db-exists.ts @@ -1,9 +1,9 @@ -import { parse } from "pg-connection-string"; -import { Client } from "pg"; +import {Client} from 'pg'; +import {parse} from 'pg-connection-string'; export const ensureDbExists = async () => { const uri = process.env.DATABASE_URI; - const { database, user, password, host, port } = parse(uri); + const {database, user, password, host, port} = parse(uri); const postgresUri = `postgres://${user}:${password}@${host}:${port}/postgres`; const client = new Client(postgresUri); diff --git a/packages/cms/src/utils/merge-queries.ts b/packages/cms/src/utils/merge-queries.ts index d95a0305..fefde78d 100644 --- a/packages/cms/src/utils/merge-queries.ts +++ b/packages/cms/src/utils/merge-queries.ts @@ -1,5 +1,5 @@ -import type * as GraphQL from "graphql"; -import { type Payload } from "payload"; +import type * as GraphQL from 'graphql'; +import {type Payload} from 'payload'; export type QueryFactory = ( graphql: typeof GraphQL, diff --git a/packages/common/.eslintrc.json b/packages/common/.eslintrc.json deleted file mode 100644 index f7e21c26..00000000 --- a/packages/common/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": ["@yestheory.family/eslint-config"], - "overrides": [ - { - "files": ["**/*.ts", "**/*.tsx"], - "parserOptions": { - "project": "./tsconfig.json" - } - } - ] -} diff --git a/packages/common/README.md b/packages/common/README.md deleted file mode 100644 index d0553e6e..00000000 --- a/packages/common/README.md +++ /dev/null @@ -1 +0,0 @@ -# common diff --git a/packages/common/package.json b/packages/common/package.json deleted file mode 100644 index d165a798..00000000 --- a/packages/common/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@yestheory.family/common", - "version": "1.0.0", - "private": true, - "scripts": { - "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --no-error-on-unmatched-pattern --max-warnings=0", - "test": "echo common: ok" - }, - "devDependencies": { - "@yestheory.family/eslint-config": "^1.0.0", - "eslint": "8.55.0", - "typescript": "5.3.2" - } -} diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json deleted file mode 100644 index 85f56e9a..00000000 --- a/packages/common/tsconfig.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ - - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} diff --git a/packages/eslint-config/.eslintrc.json b/packages/eslint-config/.eslintrc.json deleted file mode 100644 index b1e610f9..00000000 --- a/packages/eslint-config/.eslintrc.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "env": { - "es6": true, - "node": true - }, - "extends": ["eslint:recommended"], - "overrides": [ - { - "files": ["**/*.ts", "**/*.tsx"], - "env": { - "browser": true, - "es6": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], - "rules": { - "no-await-in-loop": "error", - "curly": ["error", "multi-line", "consistent"], - "default-case-last": "error", - "@typescript-eslint/default-param-last": "error", - "eqeqeq": "error", - "no-else-return": "error", - "no-sequences": "error", - "@typescript-eslint/no-throw-literal": "error", - "@typescript-eslint/require-await": "error", - "no-var": "error", - "prefer-const": "error", - "prefer-template": "error" - } - } - ] -} diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js deleted file mode 100644 index 15e78177..00000000 --- a/packages/eslint-config/index.js +++ /dev/null @@ -1,2 +0,0 @@ -const config = require("./.eslintrc.json"); -module.exports = config; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json deleted file mode 100644 index 3dbcb537..00000000 --- a/packages/eslint-config/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@yestheory.family/eslint-config", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "lint": "eslint . --max-warnings=0", - "test": "echo eslint-config: ok" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.13.1", - "@typescript-eslint/parser": "6.13.1", - "eslint": "8.55.0", - "eslint-config-prettier": "9.1.0" - } -} diff --git a/packages/server/.eslintrc.json b/packages/server/.eslintrc.json deleted file mode 100644 index f29d329c..00000000 --- a/packages/server/.eslintrc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": ["@yestheory.family/eslint-config"], - "overrides": [ - { - "files": ["**/*.ts", "**/*.tsx"], - "parserOptions": { - "project": "./tsconfig.json" - }, - "rules": { - "@typescript-eslint/no-unused-vars": [ - "warn", - { "varsIgnorePattern": "Resolver|Mutation|Query" } - ] - } - } - ] -} diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 7bceeb97..09b04a17 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -13,7 +13,6 @@ COPY yarn.lock package.json ./ # Copy package.jsons of all related packages COPY packages/common/package.json ./packages/common/ -COPY packages/eslint-config/package.json ./packages/eslint-config/ COPY packages/server/package.json ./packages/server/ # Install dependencies for the workspace diff --git a/packages/server/environment.d.ts b/packages/server/environment.d.ts index 444527bb..6025b859 100644 --- a/packages/server/environment.d.ts +++ b/packages/server/environment.d.ts @@ -1,6 +1,6 @@ declare namespace NodeJS { export interface ProcessEnv { - NODE_ENV: "development" | "production" | "test"; + NODE_ENV: 'development' | 'production' | 'test'; PRISMA_DATABASE_URL: string; FRONTEND_HOST: string; diff --git a/packages/server/eslint.config.js b/packages/server/eslint.config.js new file mode 100644 index 00000000..99d59d1d --- /dev/null +++ b/packages/server/eslint.config.js @@ -0,0 +1,25 @@ +const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin'); +const parser = require('@typescript-eslint/parser'); + +/** + * @type {import('eslint').Linter.FlatConfig[]} + */ +module.exports = [ + ...require('@atmina/linting/eslint/recommended'), + { + files: ['**/*.ts', '**/*.tsx'], + plugins: {'@typescript-eslint': typescriptEslintPlugin}, + languageOptions: { + parser, + parserOptions: { + project: ['./tsconfig.json'], + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + {varsIgnorePattern: 'Resolver|Mutation|Query'}, + ], + }, + }, +]; diff --git a/packages/server/package.json b/packages/server/package.json index e48c8d94..469d9393 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -5,10 +5,14 @@ "scripts": { "build": "tsc", "generate": "prisma generate", - "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --max-warnings=0", + "lint": "yarn run lint:fix && yarn run prettier:fix", "postinstall": "prisma generate", "start": "ts-node-dev --files src/index.ts", - "test": "echo server: ok" + "test": "echo server: ok", + "eslint:check": "eslint . --report-unused-disable-directives --max-warnings 0", + "eslint:fix": "eslint . --fix --report-unused-disable-directives --max-warnings 0", + "prettier:check": "prettier . --check", + "prettier:fix": "prettier . --write" }, "dependencies": { "@discordjs/rest": "2.2.0", @@ -39,7 +43,9 @@ "winston": "3.11.0" }, "devDependencies": { + "@atmina/linting": "^2.0.1", "@nestjs/class-validator": "0.13.4", + "@types/eslint": "^8.44.8", "@types/graphql-fields": "1.3.9", "@types/koa": "2.13.12", "@types/koa-bodyparser": "4.3.12", @@ -50,12 +56,15 @@ "@types/node": "^20.10.2", "@types/node-cron": "3.0.11", "@types/node-fetch": "2.6.9", - "@yestheory.family/eslint-config": "^1.0.0", - "eslint": "8.55.0", + "@typescript-eslint/eslint-plugin": "^6.13.2", + "@typescript-eslint/parser": "^6.13.2", + "eslint": "^8.55.0", + "prettier": "^3.1.0", "prisma": "5.5.2", "ts-node": "10.9.1", "ts-node-dev": "2.0.0", "typegraphql-prisma": "0.27.1", "typescript": "^5.3.2" - } + }, + "prettier": "@atmina/linting/prettier" } diff --git a/packages/server/src/config/grant.ts b/packages/server/src/config/grant.ts index 5294551a..99d93d4e 100644 --- a/packages/server/src/config/grant.ts +++ b/packages/server/src/config/grant.ts @@ -1,4 +1,4 @@ -import { GrantConfig } from "grant"; +import {type GrantConfig} from 'grant'; const { DISCORD_CLIENT_ID, @@ -7,12 +7,12 @@ const { FRONTEND_HOST, } = process.env; -const prefix = "/oauth"; +const prefix = '/oauth'; const config: GrantConfig = { defaults: { origin: FRONTEND_HOST, - transport: "session", + transport: 'session', prefix, }, discord: { @@ -20,7 +20,7 @@ const config: GrantConfig = { client_id: DISCORD_CLIENT_ID, client_secret: DISCORD_CLIENT_SECRET, scope: DISCORD_SCOPES?.split(/\s*,\s*/g), - response: ["tokens", "profile", "raw"], + response: ['tokens', 'profile', 'raw'], }, }; diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index c1ed70ab..e72203c9 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -1,3 +1,3 @@ -const { NODE_ENV } = process.env; +const {NODE_ENV} = process.env; -export const isDevelopment = NODE_ENV === "development"; +export const isDevelopment = NODE_ENV === 'development'; diff --git a/packages/server/src/config/session.ts b/packages/server/src/config/session.ts index ad1a8630..91a74842 100644 --- a/packages/server/src/config/session.ts +++ b/packages/server/src/config/session.ts @@ -1,22 +1,22 @@ -import { opts } from "koa-session"; -import { isDevelopment } from "./index"; -import { URL } from "url"; +import {URL} from 'url'; +import {type opts} from 'koa-session'; +import {isDevelopment} from './index'; const frontend = process.env.FRONTEND_HOST; const getRootDomain = (urlString: string) => { const frontendHostName = new URL(urlString).hostname; - const split = frontendHostName.split("."); - return split.slice(split.length - 2, split.length).join("."); + const split = frontendHostName.split('.'); + return split.slice(split.length - 2, split.length).join('.'); }; export const domain = getRootDomain(frontend); const sessionConfig: Partial = { - key: "koa.sess", + key: 'koa.sess', secure: !isDevelopment, - sameSite: "lax", - path: "/", + sameSite: 'lax', + path: '/', domain, }; diff --git a/packages/server/src/features/auth/auth-service.ts b/packages/server/src/features/auth/auth-service.ts index 984e6fa5..5e099d53 100644 --- a/packages/server/src/features/auth/auth-service.ts +++ b/packages/server/src/features/auth/auth-service.ts @@ -1,17 +1,17 @@ -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v10"; -import { Service } from "typedi"; -import { URLSearchParams } from "url"; -import { createServerLogger } from "../../services/logging/log"; -import { YtfApolloContext } from "../../types"; -import { AuthProvider } from "../user"; +import {URLSearchParams} from 'url'; +import {REST} from '@discordjs/rest'; +import {Routes} from 'discord-api-types/v10'; +import {Service} from 'typedi'; +import {createServerLogger} from '../../services/logging/log'; +import {type YtfApolloContext} from '../../types'; +import {AuthProvider} from '../user'; const refreshUrls: Record = { - [AuthProvider.DISCORD]: "https://discord.com/api/oauth2/token", + [AuthProvider.DISCORD]: 'https://discord.com/api/oauth2/token', }; const revokeUrls: Record = { - [AuthProvider.DISCORD]: "https://discord.com/api/oauth2/token/revoke", + [AuthProvider.DISCORD]: 'https://discord.com/api/oauth2/token/revoke', }; type RefreshTokenResponse = { @@ -28,7 +28,7 @@ export interface YtfAuthContext { expiresAt: number; } -export const discordAuthErrorCode = "DISCORD_AUTH_ERROR"; +export const discordAuthErrorCode = 'DISCORD_AUTH_ERROR'; const discordAuthError = new Error(discordAuthErrorCode); @Service() @@ -37,13 +37,13 @@ export class AuthService { refreshToken: string, authProvider: AuthProvider, ): Promise { - const logger = createServerLogger("authService", "refreshToken"); + const logger = createServerLogger('authService', 'refreshToken'); if (authProvider !== AuthProvider.DISCORD) { - throw new Error("Refreshing tokens is only implemented for Discord"); + throw new Error('Refreshing tokens is only implemented for Discord'); } - logger.debug("Refreshing token!"); + logger.debug('Refreshing token!'); const clientId = process.env.DISCORD_CLIENT_ID; const clientSecret = process.env.DISCORD_CLIENT_SECRET; @@ -51,22 +51,22 @@ export class AuthService { const body = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, - grant_type: "refresh_token", + grant_type: 'refresh_token', refresh_token: refreshToken, }); const response = await fetch(refreshUrls[authProvider], { - method: "POST", + method: 'POST', body, headers: { - "Content-Type": "application/x-www-form-urlencoded", + 'Content-Type': 'application/x-www-form-urlencoded', }, }); const tokenResponse = (await response.json()) as RefreshTokenResponse; if (response.status !== 200) { - logger.error("Refresh response was not positive; response is: ", { + logger.error('Refresh response was not positive; response is: ', { response, status: response.status, statusText: response.statusText, @@ -81,7 +81,7 @@ export class AuthService { const expiresAt = Date.now() + expiresIn * 1000; - return { accessToken, refreshToken: newRefreshToken, expiresAt }; + return {accessToken, refreshToken: newRefreshToken, expiresAt}; } public async invalidateToken( @@ -90,14 +90,14 @@ export class AuthService { ): Promise { const revokeUrl = revokeUrls[provider]; - const body = new URLSearchParams({ token }); + const body = new URLSearchParams({token}); await fetch(revokeUrl, { - method: "POST", + method: 'POST', body, headers: { Authorization: AuthService.getAuthHeader(), - "Content-Type": "application/x-www-form-urlencoded", + 'Content-Type': 'application/x-www-form-urlencoded', }, }); } @@ -107,7 +107,7 @@ export class AuthService { const clientSecret = process.env.DISCORD_CLIENT_SECRET; const authHeaderBase = `${clientId}:${clientSecret}`; - const authHeader = Buffer.from(authHeaderBase).toString("base64"); + const authHeader = Buffer.from(authHeaderBase).toString('base64'); return `Basic ${authHeader}`; } @@ -115,7 +115,7 @@ export class AuthService { public async ensureValidToken( ctx: YtfApolloContext, ): Promise { - const { auth, user } = ctx; + const {auth, user} = ctx; if (!auth || !user) throw discordAuthError; @@ -142,7 +142,7 @@ export class AuthService { } private async attemptTokenUsage(token: string): Promise { - const rest = new REST({ version: "10" }).setToken(token); + const rest = new REST({version: '10'}).setToken(token); try { await rest.get(Routes.user()); return true; diff --git a/packages/server/src/features/auth/discord-callback.ts b/packages/server/src/features/auth/discord-callback.ts index 17dd8dcf..a92f03ea 100644 --- a/packages/server/src/features/auth/discord-callback.ts +++ b/packages/server/src/features/auth/discord-callback.ts @@ -1,24 +1,24 @@ -import { GrantResponse } from "grant"; -import { AuthenticatedUser } from "../user"; -import { createServerLogger } from "../../services/logging/log"; -import { Middleware } from "@koa/router"; -import { YtfApolloContext } from "../../types"; +import {type Middleware} from '@koa/router'; +import {type GrantResponse} from 'grant'; +import {createServerLogger} from '../../services/logging/log'; +import {type YtfApolloContext} from '../../types'; +import {AuthenticatedUser} from '../user'; -const logger = createServerLogger("auth", "DiscordCallback"); +const logger = createServerLogger('auth', 'DiscordCallback'); -const fallbackRedirect = process.env.FRONTEND_HOST ?? "https://example.com"; +const fallbackRedirect = process.env.FRONTEND_HOST ?? 'https://example.com'; const discordCallback: Middleware = (ctx) => { - logger.debug("Received oAuth callback for Discord"); + logger.debug('Received oAuth callback for Discord'); if (!ctx.session) { - logger.error("No session found!"); - throw new Error("No session found!"); + logger.error('No session found!'); + throw new Error('No session found!'); } const response = ctx.session?.grant.response as GrantResponse; - const lastLocationKey = "last_location"; + const lastLocationKey = 'last_location'; const lastLocation = ctx.cookies.get(lastLocationKey) ?? fallbackRedirect; if (!response.raw) { @@ -31,8 +31,8 @@ const discordCallback: Middleware = (ctx) => { const expiresAt = Date.now() + parseInt(response.raw.expires_in) * 1000; ctx.session.auth = { - accessToken: response.access_token ?? "", - refreshToken: response.refresh_token ?? "", + accessToken: response.access_token ?? '', + refreshToken: response.refresh_token ?? '', expiresAt: expiresAt.toString(), }; diff --git a/packages/server/src/features/auth/graphql-auth-checker.ts b/packages/server/src/features/auth/graphql-auth-checker.ts index 56abd755..17e5db8b 100644 --- a/packages/server/src/features/auth/graphql-auth-checker.ts +++ b/packages/server/src/features/auth/graphql-auth-checker.ts @@ -1,6 +1,6 @@ -import { AuthChecker, ResolverData } from "type-graphql"; -import { YtfApolloContext } from "../../types"; -import { Guild } from "discord.js"; +import {type Guild} from 'discord.js'; +import {type AuthChecker, type ResolverData} from 'type-graphql'; +import {type YtfApolloContext} from '../../types'; export const authChecker = (guild: Guild): AuthChecker => @@ -8,7 +8,7 @@ export const authChecker = checkRolePermissions(context, roles, guild); const checkRolePermissions = ( - { context }: ResolverData, + {context}: ResolverData, roles: string[], guild: Guild, ) => { @@ -26,6 +26,6 @@ const checkRolePermissions = ( return false; } - const memberRoles = guildMember.roles.cache.map(({ name }) => name); + const memberRoles = guildMember.roles.cache.map(({name}) => name); return roles.every((role) => memberRoles.includes(role)); }; diff --git a/packages/server/src/features/auth/index.ts b/packages/server/src/features/auth/index.ts index b9a1113d..ac348eb9 100644 --- a/packages/server/src/features/auth/index.ts +++ b/packages/server/src/features/auth/index.ts @@ -1,8 +1,8 @@ -import Router from "@koa/router"; -import { grantRoutePrefix } from "../../config/grant"; -import discordCallback from "./discord-callback"; +import Router from '@koa/router'; +import {grantRoutePrefix} from '../../config/grant'; +import discordCallback from './discord-callback'; -const router = new Router({ prefix: grantRoutePrefix, methods: ["GET"] }); -router.get("/discord/callback", discordCallback); +const router = new Router({prefix: grantRoutePrefix, methods: ['GET']}); +router.get('/discord/callback', discordCallback); export default router; diff --git a/packages/server/src/features/auth/mutations/logout.ts b/packages/server/src/features/auth/mutations/logout.ts index 15fc289d..3378f102 100644 --- a/packages/server/src/features/auth/mutations/logout.ts +++ b/packages/server/src/features/auth/mutations/logout.ts @@ -1,37 +1,37 @@ -import { Authorized, Ctx, Mutation } from "type-graphql"; -import winston from "winston"; -import { domain } from "../../../config/session"; -import { Logger } from "../../../services/logging/log-service"; +import {Authorized, Ctx, Mutation} from 'type-graphql'; +import winston from 'winston'; +import {domain} from '../../../config/session'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { YtfApolloContext } from "../../../types"; -import { AuthProvider } from "../../user"; -import { AuthService, YtfAuthContext } from "../auth-service"; +} from '../../../services/resolvers/resolver-directive'; +import {YtfApolloContext} from '../../../types'; +import {type AuthProvider} from '../../user'; +import {AuthService, type YtfAuthContext} from '../auth-service'; @Resolver(ResolverTarget.PUBLIC) class LogoutMutation { constructor( - @Logger("auth", "logout") private logger: winston.Logger, + @Logger('auth', 'logout') private logger: winston.Logger, private authService: AuthService, ) {} @Authorized() @Mutation(() => Boolean) async logout(@Ctx() ctx: YtfApolloContext): Promise { - if (!ctx.user) throw new Error("Unauthenticated"); + if (!ctx.user) throw new Error('Unauthenticated'); const cookies = ctx.requestContext.cookies; await this.invalidateTokens(ctx.auth, ctx.user.type); - cookies.set("koa.sess", null, { domain }); - cookies.set("koa.sess.sig", null, { domain }); + cookies.set('koa.sess', null, {domain}); + cookies.set('koa.sess.sig', null, {domain}); return true; } private async invalidateTokens(auth: YtfAuthContext, provider: AuthProvider) { - const cookieNames = ["accessToken", "refreshToken"] as const; + const cookieNames = ['accessToken', 'refreshToken'] as const; const tokens = cookieNames .map((n) => auth[n]) diff --git a/packages/server/src/features/buddy-project/buddy-project-status.ts b/packages/server/src/features/buddy-project/buddy-project-status.ts index d4f442ef..7f790b5f 100644 --- a/packages/server/src/features/buddy-project/buddy-project-status.ts +++ b/packages/server/src/features/buddy-project/buddy-project-status.ts @@ -1,20 +1,20 @@ -import { Field, ObjectType, registerEnumType } from "type-graphql"; -import { BuddyProjectEntry } from "../../__generated__/type-graphql"; +import {Field, ObjectType, registerEnumType} from 'type-graphql'; +import {BuddyProjectEntry} from '../../__generated__/type-graphql'; export enum BuddyProjectStatus { - NOT_SIGNED_UP = "NOT_SIGNED_UP", - SIGNED_UP = "SIGNED_UP", - MATCHED = "MATCHED", + NOT_SIGNED_UP = 'NOT_SIGNED_UP', + SIGNED_UP = 'SIGNED_UP', + MATCHED = 'MATCHED', } -registerEnumType(BuddyProjectStatus, { name: "BuddyProjectStatus" }); +registerEnumType(BuddyProjectStatus, {name: 'BuddyProjectStatus'}); @ObjectType() export class BuddyProjectStatusPayload { @Field(() => BuddyProjectStatus) status: BuddyProjectStatus; - @Field(() => BuddyProjectEntry, { nullable: true }) + @Field(() => BuddyProjectEntry, {nullable: true}) buddy?: BuddyProjectEntry | null; constructor(status: BuddyProjectStatus, buddy?: BuddyProjectEntry | null) { diff --git a/packages/server/src/features/buddy-project/cron-jobs/ghost-check-cron.ts b/packages/server/src/features/buddy-project/cron-jobs/ghost-check-cron.ts index 6be4f76d..5d995b37 100644 --- a/packages/server/src/features/buddy-project/cron-jobs/ghost-check-cron.ts +++ b/packages/server/src/features/buddy-project/cron-jobs/ghost-check-cron.ts @@ -1,11 +1,11 @@ -import { Client, Guild } from "discord.js"; -import { Service } from "typedi"; -import cron from "node-cron"; -import winston from "winston"; -import { BuddyProjectEntry } from "../../../__generated__/type-graphql"; -import { Logger } from "../../../services/logging/log-service"; -import { GhostService } from "../services/ghost-service"; -import { MatchService } from "../services/match-service"; +import {Client, Guild} from 'discord.js'; +import cron from 'node-cron'; +import {Service} from 'typedi'; +import winston from 'winston'; +import {type BuddyProjectEntry} from '../../../__generated__/type-graphql'; +import {Logger} from '../../../services/logging/log-service'; +import {GhostService} from '../services/ghost-service'; +import {MatchService} from '../services/match-service'; // Hours between reporting being ghosted and being rematched const ghostedRematchDifferenceHours = 24; @@ -15,21 +15,21 @@ const ghostWarningMessageRegex = /^\*\*Buddy Project Ghosting\*\*$/gm; @Service() export class GhostCheckCron { - private static cronSchedule = "*/10 * * * *"; // Every 10 minutes + private static cronSchedule = '*/10 * * * *'; // Every 10 minutes constructor( private ghostService: GhostService, private matchService: MatchService, private guild: Guild, private client: Client, - @Logger("buddy-project", "ghost-check-cron") private logger: winston.Logger, + @Logger('buddy-project', 'ghost-check-cron') private logger: winston.Logger, ) { this.init(); } private init() { cron.schedule(GhostCheckCron.cronSchedule, () => this.checkForGhosting()); - this.logger.info("Ghost-Checker initialized"); + this.logger.info('Ghost-Checker initialized'); } async checkForGhosting() { @@ -48,7 +48,7 @@ export class GhostCheckCron { await Promise.all(ghostHandlingPromises); } - private async handleGhoster({ userId, buddyId }: BuddyProjectEntry) { + private async handleGhoster({userId, buddyId}: BuddyProjectEntry) { await this.matchService.unmatch(userId); if (buddyId) await this.ghostService.kick(buddyId); @@ -69,7 +69,7 @@ export class GhostCheckCron { const dm = await user.createDM(); // Let's hope 50 are enough :) - const lastMessages = await dm.messages.fetch({ limit: 50 }); + const lastMessages = await dm.messages.fetch({limit: 50}); const ghostWarningMessage = lastMessages.find( (m) => m.author.bot && !!m.content.match(ghostWarningMessageRegex), ); diff --git a/packages/server/src/features/buddy-project/cron-jobs/matching-cron.ts b/packages/server/src/features/buddy-project/cron-jobs/matching-cron.ts index 7a7c2e05..32747bd6 100644 --- a/packages/server/src/features/buddy-project/cron-jobs/matching-cron.ts +++ b/packages/server/src/features/buddy-project/cron-jobs/matching-cron.ts @@ -1,12 +1,12 @@ -import { Client, Guild, Message, Snowflake } from "discord.js"; -import { Service } from "typedi"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; -import { BlockService } from "../services/block-service"; -import { BuddyProjectService } from "../services/buddy-project.service"; -import { MatchService } from "../services/match-service"; -import cron from "node-cron"; -import { evenQuestions, intro, oddQuestions } from "./texts"; +import {Client, Guild, type Message, type Snowflake} from 'discord.js'; +import cron from 'node-cron'; +import {Service} from 'typedi'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; +import {BlockService} from '../services/block-service'; +import {BuddyProjectService} from '../services/buddy-project.service'; +import {MatchService} from '../services/match-service'; +import {evenQuestions, intro, oddQuestions} from './texts'; const partition = (chunkSize = 2, ...items: T[]): T[][] => { const chunks: T[][] = []; @@ -21,7 +21,7 @@ const partition = (chunkSize = 2, ...items: T[]): T[][] => { @Service() export class MatchingCron { static readonly matchAmount = 100; - static readonly cronSchedule = "*/30 * * * *"; // Every 30 minutes + static readonly cronSchedule = '*/30 * * * *'; // Every 30 minutes constructor( private matchService: MatchService, @@ -29,14 +29,14 @@ export class MatchingCron { private blockService: BlockService, private client: Client, private guild: Guild, - @Logger("buddy-project", "matching-cron") private logger: winston.Logger, + @Logger('buddy-project', 'matching-cron') private logger: winston.Logger, ) { this.init(); } init() { cron.schedule(MatchingCron.cronSchedule, () => this.runMatching()); - this.logger.info("Buddy Project-matcher initialized"); + this.logger.info('Buddy Project-matcher initialized'); } async runMatching() { @@ -73,16 +73,16 @@ export class MatchingCron { private static async explainUnfortunateCircumstances(firstMessage: Message) { await firstMessage.delete(); await firstMessage.channel.send( - "Right, this one is going to be disappointing... I had already matched you " + - "but your match had their DMs disabled, so I had to rollback everything.\n\n" + - "This message was sent to make sure you are not left wondering why I sent you a message that suddenly vanished. " + + 'Right, this one is going to be disappointing... I had already matched you ' + + 'but your match had their DMs disabled, so I had to rollback everything.\n\n' + + 'This message was sent to make sure you are not left wondering why I sent you a message that suddenly vanished. ' + "Don't worry, you are still signed up and will be matched soon (that time hopefully with better luck though)!", ); } async match([first, second]: [Snowflake, Snowflake]) { const infoChannel = this.guild.channels.cache.find( - (c) => c.name === "buddy-project-info", + (c) => c.name === 'buddy-project-info', ); try { @@ -106,7 +106,7 @@ export class MatchingCron { await MatchingCron.explainUnfortunateCircumstances(firstSentMessage); } } catch (e) { - this.logger.error("Error while matching", e); + this.logger.error('Error while matching', e); } } } diff --git a/packages/server/src/features/buddy-project/cron-jobs/texts.ts b/packages/server/src/features/buddy-project/cron-jobs/texts.ts index f686025d..0579d147 100644 --- a/packages/server/src/features/buddy-project/cron-jobs/texts.ts +++ b/packages/server/src/features/buddy-project/cron-jobs/texts.ts @@ -1,4 +1,4 @@ -import { GuildBasedChannel } from "discord.js"; +import {type GuildBasedChannel} from 'discord.js'; export const oddQuestions = `1. What were the highest and lowest points in your life so far respectively? 3. What is the one thing that you regret saying ”no” to? diff --git a/packages/server/src/features/buddy-project/mutations/block-mutations.ts b/packages/server/src/features/buddy-project/mutations/block-mutations.ts index 019fc60b..df836858 100644 --- a/packages/server/src/features/buddy-project/mutations/block-mutations.ts +++ b/packages/server/src/features/buddy-project/mutations/block-mutations.ts @@ -1,16 +1,16 @@ -import { Arg, Mutation } from "type-graphql"; +import {Arg, Mutation} from 'type-graphql'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { BlockService } from "../services/block-service"; +} from '../../../services/resolvers/resolver-directive'; +import {BlockService} from '../services/block-service'; @Resolver(ResolverTarget.YESBOT) class BlockMutations { constructor(private blockService: BlockService) {} @Mutation(() => Boolean) - public async unblock(@Arg("userId") userId: string): Promise { + public async unblock(@Arg('userId') userId: string): Promise { await this.blockService.unblock(userId); return true; } diff --git a/packages/server/src/features/buddy-project/mutations/ghost-mutations.ts b/packages/server/src/features/buddy-project/mutations/ghost-mutations.ts index 1ac628a8..43220381 100644 --- a/packages/server/src/features/buddy-project/mutations/ghost-mutations.ts +++ b/packages/server/src/features/buddy-project/mutations/ghost-mutations.ts @@ -1,15 +1,9 @@ -import { - Arg, - Field, - Mutation, - ObjectType, - registerEnumType, -} from "type-graphql"; +import {Arg, Field, Mutation, ObjectType, registerEnumType} from 'type-graphql'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { GhostService, MarkGhostedError } from "../services/ghost-service"; +} from '../../../services/resolvers/resolver-directive'; +import {GhostService, MarkGhostedError} from '../services/ghost-service'; @Resolver(ResolverTarget.YESBOT) class GhostMutations { @@ -17,11 +11,11 @@ class GhostMutations { @Mutation(() => MarkGhostedPayload) async markGhosted( - @Arg("userId") userId: string, + @Arg('userId') userId: string, ): Promise { const result = await this.ghostService.markAsGhosted(userId); - if ("error" in result) { + if ('error' in result) { return new MarkGhostedPayload(false, result.error, undefined); } @@ -29,24 +23,24 @@ class GhostMutations { } @Mutation(() => Boolean) - async markAsNotGhosting(@Arg("userId") userId: string): Promise { + async markAsNotGhosting(@Arg('userId') userId: string): Promise { await this.ghostService.markAsNotGhosting(userId); return true; } } -registerEnumType(MarkGhostedError, { name: "MarkGhostedError" }); +registerEnumType(MarkGhostedError, {name: 'MarkGhostedError'}); @ObjectType() class MarkGhostedPayload { @Field() public success: boolean; - @Field(() => MarkGhostedError, { nullable: true }) + @Field(() => MarkGhostedError, {nullable: true}) public error: MarkGhostedError | undefined; - @Field(() => String, { nullable: true }) + @Field(() => String, {nullable: true}) public buddyId: string | undefined; constructor( diff --git a/packages/server/src/features/buddy-project/mutations/set-matching-enabled.ts b/packages/server/src/features/buddy-project/mutations/set-matching-enabled.ts index 552899e3..2b975725 100644 --- a/packages/server/src/features/buddy-project/mutations/set-matching-enabled.ts +++ b/packages/server/src/features/buddy-project/mutations/set-matching-enabled.ts @@ -1,16 +1,16 @@ -import { Arg, Mutation } from "type-graphql"; +import {Arg, Mutation} from 'type-graphql'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { MatchService } from "../services/match-service"; +} from '../../../services/resolvers/resolver-directive'; +import {MatchService} from '../services/match-service'; @Resolver(ResolverTarget.YESBOT) class SetMatchingEnabledMutation { constructor(private matchService: MatchService) {} @Mutation(() => Boolean) - async setMatchingEnabled(@Arg("enabled") enabled: boolean): Promise { + async setMatchingEnabled(@Arg('enabled') enabled: boolean): Promise { await this.matchService.setEnabled(enabled); return true; diff --git a/packages/server/src/features/buddy-project/mutations/sign-out-yesbot.ts b/packages/server/src/features/buddy-project/mutations/sign-out-yesbot.ts index 09e5be06..e986f656 100644 --- a/packages/server/src/features/buddy-project/mutations/sign-out-yesbot.ts +++ b/packages/server/src/features/buddy-project/mutations/sign-out-yesbot.ts @@ -1,16 +1,16 @@ -import { Arg, Mutation } from "type-graphql"; +import {Arg, Mutation} from 'type-graphql'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { GhostService } from "../services/ghost-service"; +} from '../../../services/resolvers/resolver-directive'; +import {GhostService} from '../services/ghost-service'; @Resolver(ResolverTarget.YESBOT) class SignOutYesbotMutation { constructor(private ghostService: GhostService) {} @Mutation(() => Boolean) - public async signOut(@Arg("userId") userId: string): Promise { + public async signOut(@Arg('userId') userId: string): Promise { await this.ghostService.kick(userId); return true; diff --git a/packages/server/src/features/buddy-project/mutations/sign-up-web.ts b/packages/server/src/features/buddy-project/mutations/sign-up-web.ts index 496ce7dd..f0681ea6 100644 --- a/packages/server/src/features/buddy-project/mutations/sign-up-web.ts +++ b/packages/server/src/features/buddy-project/mutations/sign-up-web.ts @@ -1,5 +1,11 @@ -import { Prisma, PrismaClient } from "@prisma/client"; -import { Client, Guild, GuildMember, Role, Snowflake } from "discord.js"; +import {Prisma, PrismaClient} from '@prisma/client'; +import { + Client, + Guild, + type GuildMember, + type Role, + type Snowflake, +} from 'discord.js'; import { Authorized, Ctx, @@ -7,29 +13,29 @@ import { Mutation, ObjectType, registerEnumType, -} from "type-graphql"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; +} from 'type-graphql'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { YtfApolloContext } from "../../../types"; -import { AuthService } from "../../auth/auth-service"; -import { AuthProvider } from "../../user"; +} from '../../../services/resolvers/resolver-directive'; +import {YtfApolloContext} from '../../../types'; +import {AuthService} from '../../auth/auth-service'; +import {AuthProvider} from '../../user'; import { BuddyProjectStatus, BuddyProjectStatusPayload, -} from "../buddy-project-status"; -import { BuddyProjectService } from "../services/buddy-project.service"; +} from '../buddy-project-status'; +import {BuddyProjectService} from '../services/buddy-project.service'; enum SignUpResult { - FULL_SUCCESS = "FULL_SUCCESS", - SUCCESS_DMS_CLOSED = "SUCCESS_DMS_CLOSED", - FAILURE = "FAILURE", + FULL_SUCCESS = 'FULL_SUCCESS', + SUCCESS_DMS_CLOSED = 'SUCCESS_DMS_CLOSED', + FAILURE = 'FAILURE', } -registerEnumType(SignUpResult, { name: "SignUpResult" }); +registerEnumType(SignUpResult, {name: 'SignUpResult'}); @ObjectType() class WebSignUpResult { @@ -46,7 +52,7 @@ class WebSignUpResult { @Resolver(ResolverTarget.PUBLIC) class SignUpMutation { constructor( - @Logger("buddy-project", "signup") private logger: winston.Logger, + @Logger('buddy-project', 'signup') private logger: winston.Logger, private discord: Client, private guild: Guild, private prisma: PrismaClient, @@ -59,7 +65,7 @@ class SignUpMutation { public async buddyProjectSignUp( @Ctx() ctx: YtfApolloContext, ): Promise { - const { user } = ctx; + const {user} = ctx; const auth = await this.authService.ensureValidToken(ctx); @@ -68,13 +74,13 @@ class SignUpMutation { } // See https://www.prisma.io/docs/reference/api-reference/error-reference#p2002 - const uniqueConstraintFailedCode = "P2002"; + const uniqueConstraintFailedCode = 'P2002'; let member: GuildMember | undefined; try { member = await this.guild.members.fetch(user.id); } catch (e) { - this.logger.debug("Could not find requested member on the server: ", { + this.logger.debug('Could not find requested member on the server: ', { id: user.id, }); } @@ -92,7 +98,7 @@ class SignUpMutation { } try { - await this.prisma.buddyProjectEntry.create({ data: { userId: user.id } }); + await this.prisma.buddyProjectEntry.create({data: {userId: user.id}}); } catch (e) { if ( e instanceof Prisma.PrismaClientKnownRequestError && @@ -111,7 +117,7 @@ class SignUpMutation { this.logger.debug(`Signed up ${user.id} to the buddy project.`); const dmChannel = await member.createDM(); const smileEmote = - this.guild.emojis.cache.find((e) => e.name === "yesbot_smile") ?? "🦥"; + this.guild.emojis.cache.find((e) => e.name === 'yesbot_smile') ?? '🦥'; const successStatus = new BuddyProjectStatusPayload( BuddyProjectStatus.SIGNED_UP, @@ -156,6 +162,6 @@ class SignUpMutation { const bpRole = this.getBuddyProjectRole(); // Let's just blindly pretend this is going to work first try for now - await this.guild.members.add(userId, { accessToken, roles: [bpRole] }); + await this.guild.members.add(userId, {accessToken, roles: [bpRole]}); } } diff --git a/packages/server/src/features/buddy-project/mutations/sign-up-yesbot.ts b/packages/server/src/features/buddy-project/mutations/sign-up-yesbot.ts index 364a4a1e..6f382adf 100644 --- a/packages/server/src/features/buddy-project/mutations/sign-up-yesbot.ts +++ b/packages/server/src/features/buddy-project/mutations/sign-up-yesbot.ts @@ -1,31 +1,31 @@ -import { PrismaClient, Prisma } from "@prisma/client"; -import { Guild } from "discord.js"; -import { Arg, Field, Mutation, ObjectType } from "type-graphql"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; +import {PrismaClient, Prisma} from '@prisma/client'; +import {Guild} from 'discord.js'; +import {Arg, Field, Mutation, ObjectType} from 'type-graphql'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; +} from '../../../services/resolvers/resolver-directive'; @Resolver(ResolverTarget.YESBOT) class SignUpYesBotMutation { constructor( - @Logger("buddy-project", "signup-yesbot") private logger: winston.Logger, + @Logger('buddy-project', 'signup-yesbot') private logger: winston.Logger, private guild: Guild, private prisma: PrismaClient, ) {} @Mutation(() => BuddyProjectSignUpYesBotPayload) public async signUp( - @Arg("userId") userId: string, + @Arg('userId') userId: string, ): Promise { // See https://www.prisma.io/docs/reference/api-reference/error-reference#p2002 - const uniqueConstraintFailedCode = "P2002"; + const uniqueConstraintFailedCode = 'P2002'; try { await this.prisma.buddyProjectEntry.create({ - data: { userId }, + data: {userId}, }); } catch (e) { if ( @@ -34,7 +34,7 @@ class SignUpYesBotMutation { ) { return new BuddyProjectSignUpYesBotPayload( false, - "You are already signed up!", + 'You are already signed up!', ); } } @@ -43,7 +43,7 @@ class SignUpYesBotMutation { if (!member) { return new BuddyProjectSignUpYesBotPayload( false, - "I could not find you on the server!", + 'I could not find you on the server!', ); } @@ -52,7 +52,7 @@ class SignUpYesBotMutation { if (!role) { return new BuddyProjectSignUpYesBotPayload( false, - "I could not find the Buddy Project role on the server!", + 'I could not find the Buddy Project role on the server!', ); } @@ -67,7 +67,7 @@ class BuddyProjectSignUpYesBotPayload { @Field() success: boolean; - @Field(() => String, { nullable: true }) + @Field(() => String, {nullable: true}) error: string | null; constructor(success: boolean, error?: string) { diff --git a/packages/server/src/features/buddy-project/queries/buddy-project-by-user-id.ts b/packages/server/src/features/buddy-project/queries/buddy-project-by-user-id.ts index 4c5e8a7a..eebdf5b5 100644 --- a/packages/server/src/features/buddy-project/queries/buddy-project-by-user-id.ts +++ b/packages/server/src/features/buddy-project/queries/buddy-project-by-user-id.ts @@ -1,10 +1,10 @@ -import { Arg, Query } from "type-graphql"; +import {Arg, Query} from 'type-graphql'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { BuddyProjectStatusPayload } from "../buddy-project-status"; -import { BuddyProjectService } from "../services/buddy-project.service"; +} from '../../../services/resolvers/resolver-directive'; +import {BuddyProjectStatusPayload} from '../buddy-project-status'; +import {BuddyProjectService} from '../services/buddy-project.service'; @Resolver(ResolverTarget.YESBOT) class BuddyProjectByUserIdQuery { @@ -12,7 +12,7 @@ class BuddyProjectByUserIdQuery { @Query(() => BuddyProjectStatusPayload) public async getBuddy( - @Arg("userId") userId: string, + @Arg('userId') userId: string, ): Promise { return await this.buddyProjectService.getBuddyProjectStatus(userId); } diff --git a/packages/server/src/features/buddy-project/queries/buddy-project-entry-username.ts b/packages/server/src/features/buddy-project/queries/buddy-project-entry-username.ts index a6839e3b..8bc38fc3 100644 --- a/packages/server/src/features/buddy-project/queries/buddy-project-entry-username.ts +++ b/packages/server/src/features/buddy-project/queries/buddy-project-entry-username.ts @@ -1,22 +1,22 @@ -import { Guild } from "discord.js"; -import { FieldResolver, Root } from "type-graphql"; -import winston from "winston"; -import { BuddyProjectEntry } from "../../../__generated__/type-graphql"; -import { Logger } from "../../../services/logging/log-service"; +import {Guild} from 'discord.js'; +import {FieldResolver, Root} from 'type-graphql'; +import winston from 'winston'; +import {BuddyProjectEntry} from '../../../__generated__/type-graphql'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; +} from '../../../services/resolvers/resolver-directive'; @Resolver(ResolverTarget.PUBLIC, BuddyProjectEntry) class BuddyProjectEntryUsernameResolver { constructor( - @Logger("buddy-project", "buddy-project-entry-username") + @Logger('buddy-project', 'buddy-project-entry-username') private logger: winston.Logger, private guild: Guild, ) {} - @FieldResolver(() => String, { nullable: true }) + @FieldResolver(() => String, {nullable: true}) public async username( @Root() entry: BuddyProjectEntry, ): Promise { diff --git a/packages/server/src/features/buddy-project/queries/buddy-project-status-query.ts b/packages/server/src/features/buddy-project/queries/buddy-project-status-query.ts index 7cfb9146..6e19471b 100644 --- a/packages/server/src/features/buddy-project/queries/buddy-project-status-query.ts +++ b/packages/server/src/features/buddy-project/queries/buddy-project-status-query.ts @@ -1,25 +1,25 @@ -import { Authorized, Ctx, Query } from "type-graphql"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; +import {Authorized, Ctx, Query} from 'type-graphql'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { YtfApolloContext } from "../../../types"; -import { BuddyProjectStatusPayload } from "../buddy-project-status"; -import { BuddyProjectService } from "../services/buddy-project.service"; +} from '../../../services/resolvers/resolver-directive'; +import {YtfApolloContext} from '../../../types'; +import {BuddyProjectStatusPayload} from '../buddy-project-status'; +import {BuddyProjectService} from '../services/buddy-project.service'; @Resolver(ResolverTarget.PUBLIC) class BuddyProjectStatusQuery { constructor( - @Logger("buddy-project", "status") private logger: winston.Logger, + @Logger('buddy-project', 'status') private logger: winston.Logger, private buddyProjectService: BuddyProjectService, ) {} @Authorized() @Query(() => BuddyProjectStatusPayload) public async getBuddyProjectStatus( - @Ctx() { user }: YtfApolloContext, + @Ctx() {user}: YtfApolloContext, ): Promise { if (!user) throw new Error(); diff --git a/packages/server/src/features/buddy-project/services/block-service.ts b/packages/server/src/features/buddy-project/services/block-service.ts index 24649425..3b6086d9 100644 --- a/packages/server/src/features/buddy-project/services/block-service.ts +++ b/packages/server/src/features/buddy-project/services/block-service.ts @@ -1,25 +1,25 @@ -import { PrismaClient } from "@prisma/client"; -import { Client, Guild, TextChannel } from "discord.js"; -import { Service } from "typedi"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; +import {PrismaClient} from '@prisma/client'; +import {Client, Guild, type TextChannel} from 'discord.js'; +import {Service} from 'typedi'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; @Service() export class BlockService { private static readonly DMS_DISABLED_CHANNEL_NAME = - "buddy-project-dms-disabled"; + 'buddy-project-dms-disabled'; constructor( private guild: Guild, private client: Client, private prisma: PrismaClient, - @Logger("buddy-project", "block-service") private logger: winston.Logger, + @Logger('buddy-project', 'block-service') private logger: winston.Logger, ) {} private async updateBlocked(userId: string, blocked: boolean) { await this.prisma.buddyProjectEntry.update({ - where: { userId }, - data: { blocked }, + where: {userId}, + data: {blocked}, }); } @@ -40,7 +40,7 @@ export class BlockService { ); if (!disabledDmsChannel) { - const message = "Could not find disabled DMs channel"; + const message = 'Could not find disabled DMs channel'; this.logger.error(message); throw new Error(message); } @@ -58,7 +58,7 @@ export class BlockService { const ping = await disabledDmsChannel.send({ content: `<@${userId}>`, - allowedMentions: { users: [userId] }, + allowedMentions: {users: [userId]}, }); await ping.delete(); } diff --git a/packages/server/src/features/buddy-project/services/buddy-project.service.ts b/packages/server/src/features/buddy-project/services/buddy-project.service.ts index 6376778e..5a28e51f 100644 --- a/packages/server/src/features/buddy-project/services/buddy-project.service.ts +++ b/packages/server/src/features/buddy-project/services/buddy-project.service.ts @@ -1,9 +1,9 @@ -import { PrismaClient } from "@prisma/client"; -import { Service } from "typedi"; +import {PrismaClient} from '@prisma/client'; +import {Service} from 'typedi'; import { BuddyProjectStatus, BuddyProjectStatusPayload, -} from "../buddy-project-status"; +} from '../buddy-project-status'; const shuffle = (...items: T[]): T[] => { const copy = [...items]; @@ -28,11 +28,11 @@ export class BuddyProjectService { async getUnmatchedBuddyIds(limit: number): Promise { const allAvailableIds = await this.prisma.buddyProjectEntry.findMany({ - select: { userId: true }, - where: { buddyId: null, blocked: false }, + select: {userId: true}, + where: {buddyId: null, blocked: false}, }); - const shuffledIds = shuffle(...allAvailableIds.map(({ userId }) => userId)); + const shuffledIds = shuffle(...allAvailableIds.map(({userId}) => userId)); return shuffledIds.slice(0, limit); } @@ -40,8 +40,8 @@ export class BuddyProjectService { userId: string, ): Promise { const entry = await this.prisma.buddyProjectEntry.findUnique({ - where: { userId: userId }, - include: { buddy: true }, + where: {userId: userId}, + include: {buddy: true}, }); if (!entry) { diff --git a/packages/server/src/features/buddy-project/services/ghost-service.ts b/packages/server/src/features/buddy-project/services/ghost-service.ts index cbdc44dc..910002fb 100644 --- a/packages/server/src/features/buddy-project/services/ghost-service.ts +++ b/packages/server/src/features/buddy-project/services/ghost-service.ts @@ -1,6 +1,6 @@ -import { PrismaClient } from "@prisma/client"; -import { Service } from "typedi"; -import { BuddyProjectEntry } from "../../../__generated__/type-graphql"; +import {PrismaClient} from '@prisma/client'; +import {Service} from 'typedi'; +import {type BuddyProjectEntry} from '../../../__generated__/type-graphql'; export enum MarkGhostedError { NOT_SIGNED_UP, @@ -21,48 +21,48 @@ export class GhostService { async getGhostedBefore(before: Date): Promise { return this.prisma.buddyProjectEntry.findMany({ - where: { reportedGhostDate: { lte: before } }, + where: {reportedGhostDate: {lte: before}}, }); } async markAsGhosted( userId: string, - ): Promise<{ error: MarkGhostedError } | { buddyId: string }> { + ): Promise<{error: MarkGhostedError} | {buddyId: string}> { const existingEntry = await this.prisma.buddyProjectEntry.findUnique({ - where: { userId }, + where: {userId}, select: { matchedDate: true, reportedGhostDate: true, ghostReportCount: true, buddyId: true, buddy: { - select: { reportedGhostDate: true, confirmedNotGhostingDate: true }, + select: {reportedGhostDate: true, confirmedNotGhostingDate: true}, }, }, }); - if (!existingEntry) return { error: MarkGhostedError.NOT_SIGNED_UP }; + if (!existingEntry) return {error: MarkGhostedError.NOT_SIGNED_UP}; - const { matchedDate, reportedGhostDate, ghostReportCount } = existingEntry; + const {matchedDate, reportedGhostDate, ghostReportCount} = existingEntry; if (!matchedDate || !existingEntry.buddyId) { - return { error: MarkGhostedError.NOT_MATCHED }; + return {error: MarkGhostedError.NOT_MATCHED}; } - if (reportedGhostDate) return { error: MarkGhostedError.ALREADY_MARKED }; + if (reportedGhostDate) return {error: MarkGhostedError.ALREADY_MARKED}; if (ghostReportCount >= 2) { - return { error: MarkGhostedError.MARKED_TOO_OFTEN }; + return {error: MarkGhostedError.MARKED_TOO_OFTEN}; } if (existingEntry.buddy?.reportedGhostDate !== null) { - return { error: MarkGhostedError.BUDDY_MARKED_ALREADY }; + return {error: MarkGhostedError.BUDDY_MARKED_ALREADY}; } const timeSinceMatching = Date.now() - matchedDate.getTime(); const requiredTime = matchedGhostedDifferenceHours * 60 * 60 * 1000; if (timeSinceMatching < requiredTime) { - return { error: MarkGhostedError.WAITED_TOO_LITTLE_AFTER_MATCH }; + return {error: MarkGhostedError.WAITED_TOO_LITTLE_AFTER_MATCH}; } const lastConfirmedNotGhosting = @@ -70,35 +70,35 @@ export class GhostService { const timeSinceNotGhostConfirmation = Date.now() - lastConfirmedNotGhosting; if (timeSinceNotGhostConfirmation < requiredTime) { - return { error: MarkGhostedError.WAITED_TOO_LITTLE_AFTER_GHOST }; + return {error: MarkGhostedError.WAITED_TOO_LITTLE_AFTER_GHOST}; } await this.prisma.buddyProjectEntry.update({ - where: { userId }, + where: {userId}, data: { reportedGhostDate: new Date(), ghostReportCount: ghostReportCount + 1, }, }); - return { buddyId: existingEntry.buddyId }; + return {buddyId: existingEntry.buddyId}; } async markAsNotGhosting(userId: string) { const clearGhostDate = this.prisma.buddyProjectEntry.update({ - where: { buddyId: userId }, - data: { reportedGhostDate: null }, + where: {buddyId: userId}, + data: {reportedGhostDate: null}, }); const updateConfirmNotGhosting = this.prisma.buddyProjectEntry.update({ - where: { userId }, - data: { confirmedNotGhostingDate: new Date() }, + where: {userId}, + data: {confirmedNotGhostingDate: new Date()}, }); await this.prisma.$transaction([clearGhostDate, updateConfirmNotGhosting]); } async kick(userId: string) { - await this.prisma.buddyProjectEntry.delete({ where: { userId } }); + await this.prisma.buddyProjectEntry.delete({where: {userId}}); } } diff --git a/packages/server/src/features/buddy-project/services/match-service.ts b/packages/server/src/features/buddy-project/services/match-service.ts index dd0c5c54..dbf0e18b 100644 --- a/packages/server/src/features/buddy-project/services/match-service.ts +++ b/packages/server/src/features/buddy-project/services/match-service.ts @@ -1,10 +1,10 @@ -import { PrismaClient, PrismaPromise } from "@prisma/client"; -import { Service } from "typedi"; -import { KeyValueStore } from "../../../services/key-value-store"; +import {PrismaClient, type PrismaPromise} from '@prisma/client'; +import {Service} from 'typedi'; +import {KeyValueStore} from '../../../services/key-value-store'; @Service() export class MatchService { - private static readonly _enabledKey = "buddy-project-matching-enabled"; + private static readonly _enabledKey = 'buddy-project-matching-enabled'; constructor( private prisma: PrismaClient, @@ -14,7 +14,7 @@ export class MatchService { async isEnabled(): Promise { const value = await this.keyValueStore.get(MatchService._enabledKey); - return value === "true"; + return value === 'true'; } async setEnabled(enabled: boolean): Promise { @@ -25,15 +25,15 @@ export class MatchService { const userIds = Array.isArray(userId) ? userId : [userId]; await this.prisma.buddyProjectEntry.updateMany({ - where: { userId: { in: userIds } }, - data: { buddyId: null, matchedDate: null, reportedGhostDate: null }, + where: {userId: {in: userIds}}, + data: {buddyId: null, matchedDate: null, reportedGhostDate: null}, }); } private matchWith(userId: string, buddyId: string): PrismaPromise { return this.prisma.buddyProjectEntry.update({ - where: { userId }, - data: { buddyId, matchedDate: new Date() }, + where: {userId}, + data: {buddyId, matchedDate: new Date()}, }); } diff --git a/packages/server/src/features/discord/index.ts b/packages/server/src/features/discord/index.ts index 68f5656b..70ac0d5e 100644 --- a/packages/server/src/features/discord/index.ts +++ b/packages/server/src/features/discord/index.ts @@ -1,7 +1,7 @@ -import { Client, Guild, IntentsBitField } from "discord.js"; -import { createServerLogger } from "../../services/logging/log"; +import {Client, type Guild, IntentsBitField} from 'discord.js'; +import {createServerLogger} from '../../services/logging/log'; -const logger = createServerLogger("discord", "index"); +const logger = createServerLogger('discord', 'index'); const client = new Client({ intents: [IntentsBitField.Flags.Guilds, IntentsBitField.Flags.GuildMembers], @@ -15,26 +15,26 @@ interface DiscordValues { export const initialize = async (skipLogin = false): Promise => { const guildId = process.env.DISCORD_TARGET_GUILD; if (!guildId) { - throw new Error("Guild ID not specified!"); + throw new Error('Guild ID not specified!'); } if (!skipLogin) { - logger.info("Logging in with Discord client"); + logger.info('Logging in with Discord client'); await client.login(process.env.DISCORD_BOT_TOKEN); } if (!client.readyAt) { logger.info( - "Client is not ready, delaying, retrying remaining initialization in a second.", + 'Client is not ready, delaying, retrying remaining initialization in a second.', ); return new Promise((res, rej) => { setTimeout(() => initialize(true).then(res).catch(rej), 1000); }); } - logger.debug("Client is ready, fetching guild!"); + logger.debug('Client is ready, fetching guild!'); const guild = await client.guilds.fetch(guildId); - logger.info("Client initialized!"); + logger.info('Client initialized!'); - return { client, guild }; + return {client, guild}; }; diff --git a/packages/server/src/features/index.ts b/packages/server/src/features/index.ts index 9460e616..9deca22e 100644 --- a/packages/server/src/features/index.ts +++ b/packages/server/src/features/index.ts @@ -1,3 +1,3 @@ -export * from "./user"; -export * as Discord from "./discord"; -export { default as authenticationRouter } from "./auth"; +export * from './user'; +export * as Discord from './discord'; +export {default as authenticationRouter} from './auth'; diff --git a/packages/server/src/features/user/authenticated-user.ts b/packages/server/src/features/user/authenticated-user.ts index eeff8cd5..8d8b799c 100644 --- a/packages/server/src/features/user/authenticated-user.ts +++ b/packages/server/src/features/user/authenticated-user.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType } from "type-graphql"; +import {Field, ObjectType} from 'type-graphql'; export interface DiscordProfile { id: string; @@ -17,7 +17,7 @@ export enum AuthProvider { export class AuthenticatedUser { @Field() id: string; @Field() username: string; - @Field(() => String, { nullable: true }) avatarUrl: string | null; + @Field(() => String, {nullable: true}) avatarUrl: string | null; @Field() type: AuthProvider; constructor( diff --git a/packages/server/src/features/user/index.ts b/packages/server/src/features/user/index.ts index 864c998e..24458447 100644 --- a/packages/server/src/features/user/index.ts +++ b/packages/server/src/features/user/index.ts @@ -1 +1 @@ -export * from "./authenticated-user"; +export * from './authenticated-user'; diff --git a/packages/server/src/features/user/queries/me.ts b/packages/server/src/features/user/queries/me.ts index 019d0275..53f33ab3 100644 --- a/packages/server/src/features/user/queries/me.ts +++ b/packages/server/src/features/user/queries/me.ts @@ -1,20 +1,20 @@ -import { Ctx, Query } from "type-graphql"; -import { AuthenticatedUser } from "../authenticated-user"; -import { YtfApolloContext } from "../../../types"; -import { Logger } from "../../../services/logging/log-service"; -import winston from "winston"; +import {Ctx, Query} from 'type-graphql'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; +} from '../../../services/resolvers/resolver-directive'; +import {YtfApolloContext} from '../../../types'; +import {AuthenticatedUser} from '../authenticated-user'; -@Resolver(ResolverTarget.PUBLIC, () => AuthenticatedUser) +@Resolver(ResolverTarget.PUBLIC, AuthenticatedUser) class MeResolver { - constructor(@Logger("user", "Me") private logger: winston.Logger) {} + constructor(@Logger('user', 'Me') private logger: winston.Logger) {} - @Query(() => AuthenticatedUser, { nullable: true }) - me(@Ctx() { user }: YtfApolloContext): AuthenticatedUser | null { - this.logger.debug("Returning user", user); + @Query(() => AuthenticatedUser, {nullable: true}) + me(@Ctx() {user}: YtfApolloContext): AuthenticatedUser | null { + this.logger.debug('Returning user', user); return user; } } diff --git a/packages/server/src/features/user/queries/user-server-state.ts b/packages/server/src/features/user/queries/user-server-state.ts index 2ad6d98a..52a59d58 100644 --- a/packages/server/src/features/user/queries/user-server-state.ts +++ b/packages/server/src/features/user/queries/user-server-state.ts @@ -1,17 +1,17 @@ -import { Guild } from "discord.js"; -import { Authorized, FieldResolver, Root } from "type-graphql"; -import winston from "winston"; -import { Logger } from "../../../services/logging/log-service"; +import {Guild} from 'discord.js'; +import {Authorized, FieldResolver, Root} from 'type-graphql'; +import winston from 'winston'; +import {Logger} from '../../../services/logging/log-service'; import { Resolver, ResolverTarget, -} from "../../../services/resolvers/resolver-directive"; -import { AuthenticatedUser, AuthProvider } from "../authenticated-user"; +} from '../../../services/resolvers/resolver-directive'; +import {AuthenticatedUser, AuthProvider} from '../authenticated-user'; @Resolver(ResolverTarget.PUBLIC, AuthenticatedUser) class UserServerStateResolver { constructor( - @Logger("user", "server-state") private logger: winston.Logger, + @Logger('user', 'server-state') private logger: winston.Logger, private guild: Guild, ) {} diff --git a/packages/server/src/graphql-directives.ts b/packages/server/src/graphql-directives.ts index b486aa34..20ce2bbf 100644 --- a/packages/server/src/graphql-directives.ts +++ b/packages/server/src/graphql-directives.ts @@ -1,7 +1,7 @@ -import { DirectiveLocation, GraphQLDirective, GraphQLString } from "graphql"; +import {DirectiveLocation, GraphQLDirective, GraphQLString} from 'graphql'; export const ExportDirective = new GraphQLDirective({ - name: "export", + name: 'export', args: { exportName: { type: GraphQLString, diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1f53bcb8..4114cfe9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,18 +1,18 @@ -import { PrismaClient } from "@prisma/client"; -import { Client, Guild } from "discord.js"; -import { config } from "dotenv"; -import "reflect-metadata"; -import { Container } from "typedi"; -import { Discord } from "./features"; -import { launchPublicServer } from "./servers"; -import { launchYesBotServer } from "./servers/yesbot"; -import { launchYesBotSchemaServer } from "./servers/yesbot-schema"; -import { CronStartSideEffect } from "./services/cron/cron-start-side-effect"; -import { createServerLogger } from "./services/logging/log"; +import {PrismaClient} from '@prisma/client'; +import {Client, Guild} from 'discord.js'; +import {config} from 'dotenv'; +import 'reflect-metadata'; +import {Container} from 'typedi'; +import {Discord} from './features'; +import {launchPublicServer} from './servers'; +import {launchYesBotServer} from './servers/yesbot'; +import {launchYesBotSchemaServer} from './servers/yesbot-schema'; +import {CronStartSideEffect} from './services/cron/cron-start-side-effect'; +import {createServerLogger} from './services/logging/log'; config(); -const logger = createServerLogger("src", "index"); +const logger = createServerLogger('src', 'index'); const prisma = new PrismaClient(); @@ -20,7 +20,7 @@ const main = async () => { Container.set(PrismaClient, prisma); if (!process.env.IS_E2E) { - const { client, guild } = await Discord.initialize(); + const {client, guild} = await Discord.initialize(); Container.set(Client, client); Container.set(Guild, guild); } @@ -34,4 +34,4 @@ const main = async () => { } }; -main().then(() => logger.debug("Launched server")); +main().then(() => logger.debug('Launched server')); diff --git a/packages/server/src/servers/index.ts b/packages/server/src/servers/index.ts index 153b889a..af4ff0bf 100644 --- a/packages/server/src/servers/index.ts +++ b/packages/server/src/servers/index.ts @@ -1 +1 @@ -export { launchPublicServer } from "./public"; +export {launchPublicServer} from './public'; diff --git a/packages/server/src/servers/public.ts b/packages/server/src/servers/public.ts index b6d19008..75d106ea 100644 --- a/packages/server/src/servers/public.ts +++ b/packages/server/src/servers/public.ts @@ -1,31 +1,31 @@ -import { ApolloServer, gql } from "apollo-server-koa"; -import { Guild } from "discord.js"; -import grant from "grant"; -import Koa, { Context } from "koa"; -import mount from "koa-mount"; -import koaSession from "koa-session"; -import { BuildSchemaOptions, buildSchemaSync } from "type-graphql"; -import { Container } from "typedi"; -import { isDevelopment } from "../config"; -import grantConfig from "../config/grant"; -import sessionConfig from "../config/session"; -import { authenticationRouter } from "../features"; -import { discordAuthErrorCode } from "../features/auth/auth-service"; -import { authChecker } from "../features/auth/graphql-auth-checker"; -import { ExportDirective } from "../graphql-directives"; -import { createServerLogger } from "../services/logging/log"; +import {delegateToSchema} from '@graphql-tools/delegate'; +import {buildHTTPExecutor} from '@graphql-tools/executor-http'; +import {stitchSchemas} from '@graphql-tools/stitch'; +import {schemaFromExecutor} from '@graphql-tools/wrap'; +import {ApolloServer, gql} from 'apollo-server-koa'; +import {Guild} from 'discord.js'; +import grant from 'grant'; +import {OperationTypeNode} from 'graphql/language/ast'; +import Koa, {type Context} from 'koa'; +import mount from 'koa-mount'; +import koaSession from 'koa-session'; +import {type BuildSchemaOptions, buildSchemaSync} from 'type-graphql'; +import {Container} from 'typedi'; +import {isDevelopment} from '../config'; +import grantConfig from '../config/grant'; +import sessionConfig from '../config/session'; +import {authenticationRouter} from '../features'; +import {discordAuthErrorCode} from '../features/auth/auth-service'; +import {authChecker} from '../features/auth/graphql-auth-checker'; +import {ExportDirective} from '../graphql-directives'; +import {createServerLogger} from '../services/logging/log'; import { getResolvers, ResolverTarget, -} from "../services/resolvers/resolver-directive"; -import { YtfApolloContext } from "../types"; -import { buildHTTPExecutor } from "@graphql-tools/executor-http"; -import { stitchSchemas } from "@graphql-tools/stitch"; -import { schemaFromExecutor } from "@graphql-tools/wrap"; -import { delegateToSchema } from "@graphql-tools/delegate"; -import { OperationTypeNode } from "graphql/language/ast"; +} from '../services/resolvers/resolver-directive'; +import {type YtfApolloContext} from '../types'; -const logger = createServerLogger("server", "public"); +const logger = createServerLogger('server', 'public'); export const launchPublicServer = async () => { const additionalOptions: Partial = {}; @@ -71,8 +71,8 @@ export const launchPublicServer = async () => { return delegateToSchema({ schema: cmsSchema, operation: OperationTypeNode.QUERY, - fieldName: "searchTokenByAuthenticated", - args: { isAuthenticated: !!context.user }, + fieldName: 'searchTokenByAuthenticated', + args: {isAuthenticated: !!context.user}, context, info, }); @@ -82,13 +82,13 @@ export const launchPublicServer = async () => { }, }); - const port = process.env["BACKEND_PORT"] ?? 5000; + const port = process.env['BACKEND_PORT'] ?? 5000; const koaGrant = grant.koa(); const app = new Koa(); - app.keys = ["grant"]; + app.keys = ['grant']; app.proxy = !isDevelopment; app.use(koaSession(sessionConfig, app)); - app.use(mount("/oauth", koaGrant(grantConfig))); + app.use(mount('/oauth', koaGrant(grantConfig))); app.use(authenticationRouter.routes()); const server = new ApolloServer({ @@ -118,12 +118,12 @@ export const launchPublicServer = async () => { const authErrors = response.errors?.filter((e) => - e.message.startsWith("Access denied!"), + e.message.startsWith('Access denied!'), ) ?? []; const user = (reqContext.context as YtfApolloContext).user; for (const authError of authErrors) { - const code = user ? "UNAUTHORIZED" : "UNAUTHENTICATED"; + const code = user ? 'UNAUTHORIZED' : 'UNAUTHENTICATED'; if (authError.extensions) authError.extensions.code = code; } @@ -133,7 +133,7 @@ export const launchPublicServer = async () => { (e) => !discordAuthError.includes(e) && !authErrors.includes(e), ) ?? []; if (unknownErrors.length > 0) { - logger.error("Error executing graphql resolver", unknownErrors); + logger.error('Error executing graphql resolver', unknownErrors); } return response; @@ -142,10 +142,10 @@ export const launchPublicServer = async () => { await server.start(); server.applyMiddleware({ app, - cors: { origin: process.env.FRONTEND_HOST, credentials: true }, + cors: {origin: process.env.FRONTEND_HOST, credentials: true}, }); - app.listen({ port }, () => logger.info(`Backend listening on port ${port}`)); + app.listen({port}, () => logger.info(`Backend listening on port ${port}`)); return app; }; diff --git a/packages/server/src/servers/yesbot-schema.ts b/packages/server/src/servers/yesbot-schema.ts index 8b6d39cf..e9f162b4 100644 --- a/packages/server/src/servers/yesbot-schema.ts +++ b/packages/server/src/servers/yesbot-schema.ts @@ -1,10 +1,10 @@ -import Koa from "koa"; -import { getIntrospectionQuery } from "graphql"; -import { isDevelopment } from "../config"; -import { createServerLogger } from "../services/logging/log"; -import Router from "@koa/router"; +import Router from '@koa/router'; +import {getIntrospectionQuery} from 'graphql'; +import Koa from 'koa'; +import {isDevelopment} from '../config'; +import {createServerLogger} from '../services/logging/log'; -const logger = createServerLogger("server", "yesbot-schema"); +const logger = createServerLogger('server', 'yesbot-schema'); // To avoid exposing the entirety of YesBot's API to this backend, this server acts as a proxy to fetch the schema in CI export const launchYesBotSchemaServer = () => { @@ -17,16 +17,16 @@ export const launchYesBotSchemaServer = () => { app.proxy = !isDevelopment; const router = new Router(); - router.post("/_yesbot-schema", async (ctx) => { - const introspectionQuery = getIntrospectionQuery({ descriptions: false }); + router.post('/_yesbot-schema', async (ctx) => { + const introspectionQuery = getIntrospectionQuery({descriptions: false}); const response = await fetch( `http://127.0.0.1:${yesbotServerPort}/graphql`, { - method: "POST", - body: JSON.stringify({ query: introspectionQuery }), + method: 'POST', + body: JSON.stringify({query: introspectionQuery}), headers: { - "Content-Type": "application/json", - "x-yesbot-authentication": process.env.YESBOT_API_TOKEN, + 'Content-Type': 'application/json', + 'x-yesbot-authentication': process.env.YESBOT_API_TOKEN, }, }, ); @@ -37,7 +37,7 @@ export const launchYesBotSchemaServer = () => { app.use(router.routes()); app.use(router.allowedMethods()); - app.listen({ port: thisPort }, () => + app.listen({port: thisPort}, () => logger.info(`Backend listening on port ${thisPort}`), ); }; diff --git a/packages/server/src/servers/yesbot.ts b/packages/server/src/servers/yesbot.ts index 2cb6f784..3385b263 100644 --- a/packages/server/src/servers/yesbot.ts +++ b/packages/server/src/servers/yesbot.ts @@ -1,19 +1,19 @@ -import { ApolloServer } from "apollo-server-koa"; -import Koa, { Middleware } from "koa"; -import { buildSchemaSync } from "type-graphql"; -import { Container } from "typedi"; -import { isDevelopment } from "../config"; -import { ExportDirective } from "../graphql-directives"; -import { createServerLogger } from "../services/logging/log"; +import {ApolloServer} from 'apollo-server-koa'; +import Koa, {type Middleware} from 'koa'; +import {buildSchemaSync} from 'type-graphql'; +import {Container} from 'typedi'; +import {isDevelopment} from '../config'; +import {ExportDirective} from '../graphql-directives'; +import {createServerLogger} from '../services/logging/log'; import { getResolvers, ResolverTarget, -} from "../services/resolvers/resolver-directive"; +} from '../services/resolvers/resolver-directive'; -const logger = createServerLogger("server", "yesbot"); +const logger = createServerLogger('server', 'yesbot'); -const requireValidToken: Middleware = async ({ headers, res }, next) => { - const yesbotAuthHeader = headers["x-yesbot-authentication"] ?? ""; +const requireValidToken: Middleware = async ({headers, res}, next) => { + const yesbotAuthHeader = headers['x-yesbot-authentication'] ?? ''; const requiredValue = process.env.YESBOT_API_TOKEN; if (yesbotAuthHeader !== requiredValue) { @@ -47,7 +47,7 @@ export const launchYesBotServer = async () => { csrfPrevention: true, formatResponse: (response) => { if (response.errors) { - logger.error("Error executing graphql resolver", response.errors); + logger.error('Error executing graphql resolver', response.errors); } return response; @@ -55,9 +55,9 @@ export const launchYesBotServer = async () => { }); await server.start(); - server.applyMiddleware({ app }); + server.applyMiddleware({app}); - app.listen({ port }, () => logger.info(`Backend listening on port ${port}`)); + app.listen({port}, () => logger.info(`Backend listening on port ${port}`)); return app; }; diff --git a/packages/server/src/services/cron/cron-start-side-effect.ts b/packages/server/src/services/cron/cron-start-side-effect.ts index e6bda2cf..5f8b396e 100644 --- a/packages/server/src/services/cron/cron-start-side-effect.ts +++ b/packages/server/src/services/cron/cron-start-side-effect.ts @@ -1,6 +1,6 @@ -import { Service } from "typedi"; -import { MatchingCron } from "../../features/buddy-project/cron-jobs/matching-cron"; -import { GhostCheckCron } from "../../features/buddy-project/cron-jobs/ghost-check-cron"; +import {Service} from 'typedi'; +import {GhostCheckCron} from '../../features/buddy-project/cron-jobs/ghost-check-cron'; +import {MatchingCron} from '../../features/buddy-project/cron-jobs/matching-cron'; @Service() export class CronStartSideEffect { diff --git a/packages/server/src/services/key-value-store.ts b/packages/server/src/services/key-value-store.ts index d324d09d..54c770eb 100644 --- a/packages/server/src/services/key-value-store.ts +++ b/packages/server/src/services/key-value-store.ts @@ -1,21 +1,21 @@ -import { PrismaClient } from "@prisma/client"; -import { Service } from "typedi"; +import {PrismaClient} from '@prisma/client'; +import {Service} from 'typedi'; @Service() export class KeyValueStore { constructor(private readonly prisma: PrismaClient) {} async get(key: string): Promise { - const entity = await this.prisma.keyValue.findUnique({ where: { key } }); + const entity = await this.prisma.keyValue.findUnique({where: {key}}); return entity?.value ?? null; } async set(key: string, value: string) { await this.prisma.keyValue.upsert({ - where: { key }, - update: { value }, - create: { key, value }, + where: {key}, + update: {value}, + create: {key, value}, }); } } diff --git a/packages/server/src/services/logging/log-service.ts b/packages/server/src/services/logging/log-service.ts index cae18edc..51657a30 100644 --- a/packages/server/src/services/logging/log-service.ts +++ b/packages/server/src/services/logging/log-service.ts @@ -1,7 +1,7 @@ -import "reflect-metadata"; -import { Container } from "typedi"; -import { createServerLogger } from "./log"; -import { Constructable } from "typedi/types/types/constructable.type"; +import 'reflect-metadata'; +import {Container} from 'typedi'; +import {type Constructable} from 'typedi/types/types/constructable.type'; +import {createServerLogger} from './log'; export function Logger(kind: string, program: string) { return function ( diff --git a/packages/server/src/services/logging/log.ts b/packages/server/src/services/logging/log.ts index bcd0c7ed..49d322ea 100644 --- a/packages/server/src/services/logging/log.ts +++ b/packages/server/src/services/logging/log.ts @@ -1,12 +1,13 @@ -import winston, { createLogger, format, transports } from "winston"; +import type winston from 'winston'; +import {createLogger, format, transports} from 'winston'; const USE_COLORS = process.stdout.isTTY; const SHOW_TIMESTAMP = process.stdout.isTTY; -const fmt = format.printf(({ level, message, timestamp, ...meta }) => { - const { kind, program, ...fields } = meta; +const fmt = format.printf(({level, message, timestamp, ...meta}) => { + const {kind, program, ...fields} = meta; - let out = ""; + let out = ''; if (SHOW_TIMESTAMP) { out += `${timestamp} `; } @@ -31,7 +32,7 @@ if (USE_COLORS) { if (SHOW_TIMESTAMP) { formatters.push( format.timestamp({ - format: "HH:mm:ss.SSS", + format: 'HH:mm:ss.SSS', }), ); } @@ -40,7 +41,7 @@ if (SHOW_TIMESTAMP) { formatters.push(fmt); const loggerOpts: winston.LoggerOptions = { - level: process.env.NODE_ENV === "production" ? "info" : "debug", + level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: format.combine(...formatters), transports: [new transports.Console()], }; diff --git a/packages/server/src/services/resolvers/resolver-directive.ts b/packages/server/src/services/resolvers/resolver-directive.ts index bec593d0..b084c69f 100644 --- a/packages/server/src/services/resolvers/resolver-directive.ts +++ b/packages/server/src/services/resolvers/resolver-directive.ts @@ -1,19 +1,15 @@ -import { Service } from "typedi"; -import { - ClassType, - NonEmptyArray, - Resolver as OriginalResolver, -} from "type-graphql"; -import { createServerLogger } from "../logging/log"; -import { glob } from "glob"; -import path from "path"; -import { ClassTypeResolver } from "type-graphql/build/typings/decorators/types"; - -type Class = { new (...args: never[]): unknown }; +import path from 'path'; +import {glob} from 'glob'; +import {type NonEmptyArray, Resolver as OriginalResolver} from 'type-graphql'; +import {Service} from 'typedi'; +import {createServerLogger} from '../logging/log'; + +type Class = {new (...args: never[]): unknown}; +type OriginalResolverArgument = Parameters[0]; export const enum ResolverTarget { - YESBOT = "YESBOT", - PUBLIC = "PUBLIC", + YESBOT = 'YESBOT', + PUBLIC = 'PUBLIC', } const resolvers: Record = { @@ -21,11 +17,11 @@ const resolvers: Record = { [ResolverTarget.PUBLIC]: [], }; -const logger = createServerLogger("services", "resolver"); +const logger = createServerLogger('services', 'resolver'); export const Resolver = ( resolverTarget: ResolverTarget | ResolverTarget[], - args?: ClassTypeResolver | ClassType | undefined, + args?: OriginalResolverArgument, ) => { const resolverTargets = Array.isArray(resolverTarget) ? resolverTarget @@ -40,7 +36,6 @@ export const Resolver = ( Service()(target); - // @ts-expect-error lol if (args) OriginalResolver(args)(target); else OriginalResolver()(target); @@ -49,24 +44,24 @@ export const Resolver = ( }; const collectResolvers = async (): Promise => { - logger.info("Collecting resolvers"); + logger.info('Collecting resolvers'); - const extension = process.env.NODE_ENV === "production" ? ".js" : ".ts"; - const baseDirectory = process.env.NODE_ENV === "production" ? "dist" : "src"; + const extension = process.env.NODE_ENV === 'production' ? '.js' : '.ts'; + const baseDirectory = process.env.NODE_ENV === 'production' ? 'dist' : 'src'; let matches: string[]; try { matches = await glob(`${baseDirectory}/features/**/*${extension}`); } catch (e) { - logger.error("Error loading resolvers: ", e); + logger.error('Error loading resolvers: ', e); throw e; } const importPromises = matches.map((p) => { - const split = p.split("."); + const split = p.split('.'); split.unshift(); - const modulePath = path.join(process.cwd(), split.join(".")); + const modulePath = path.join(process.cwd(), split.join('.')); return import(modulePath); }); @@ -74,11 +69,11 @@ const collectResolvers = async (): Promise => { try { await Promise.all(importPromises); } catch (e) { - logger.error("Error loading resolvers: ", e); + logger.error('Error loading resolvers: ', e); throw e; } - logger.debug("Loading complete!"); + logger.debug('Loading complete!'); }; export const getResolvers = async ( @@ -92,7 +87,7 @@ export const getResolvers = async ( if (resolversForTarget.length === 0) { throw new Error( - "No resolver was loaded, make sure at least one resolver is tagged with the Resolver decorator from the service directory!", + 'No resolver was loaded, make sure at least one resolver is tagged with the Resolver decorator from the service directory!', ); } diff --git a/packages/server/src/types/env.d.ts b/packages/server/src/types/env.d.ts index 546e68d2..3c35b167 100644 --- a/packages/server/src/types/env.d.ts +++ b/packages/server/src/types/env.d.ts @@ -1,6 +1,6 @@ declare namespace NodeJS { export interface ProcessEnv { - NODE_ENV: "development" | "production" | "test"; + NODE_ENV: 'development' | 'production' | 'test'; IS_E2E: string; FRONTEND_HOST: string; PRISMA_DATABASE_URL: string; diff --git a/packages/server/src/types/index.ts b/packages/server/src/types/index.ts index f3f2586b..63160e80 100644 --- a/packages/server/src/types/index.ts +++ b/packages/server/src/types/index.ts @@ -1,6 +1,6 @@ -import { AuthenticatedUser } from "../features"; -import koa from "koa"; -import { YtfAuthContext } from "../features/auth/auth-service"; +import type koa from 'koa'; +import {type AuthenticatedUser} from '../features'; +import {type YtfAuthContext} from '../features/auth/auth-service'; export interface YtfApolloContext { user: AuthenticatedUser | null; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index a9724561..e49effcc 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -25,6 +25,7 @@ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, + "include": ["./src/**/*.ts", "environment.d.ts"], "ts-node": { "files": true } diff --git a/packages/web/.eslintrc.json b/packages/web/.eslintrc.json index 54e6219a..a53bbeff 100644 --- a/packages/web/.eslintrc.json +++ b/packages/web/.eslintrc.json @@ -1,6 +1,5 @@ { "extends": [ - "@yestheory.family/eslint-config", "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:@next/next/recommended", diff --git a/packages/web/.prettierignore b/packages/web/.prettierignore new file mode 100644 index 00000000..3fde1fa8 --- /dev/null +++ b/packages/web/.prettierignore @@ -0,0 +1,25 @@ +node_modules +build +dist +storybook-static +.next +.turbo +.yarn + +package.json + +# Lockfiles +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Generated files +generated +__generated__ +*.generated* + +# IDE +.idea +.vscode + +.DS_Store diff --git a/packages/web/.storybook/main.ts b/packages/web/.storybook/main.ts index a5cacc6e..65b95d11 100644 --- a/packages/web/.storybook/main.ts +++ b/packages/web/.storybook/main.ts @@ -1,20 +1,20 @@ -import path from "node:path"; -import type { StorybookConfig } from "@storybook/nextjs"; -import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"; +import path from 'node:path'; +import type {StorybookConfig} from '@storybook/nextjs'; +import {TsconfigPathsPlugin} from 'tsconfig-paths-webpack-plugin'; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', ], framework: { - name: "@storybook/nextjs", + name: '@storybook/nextjs', options: {}, }, docs: { - autodocs: "tag", + autodocs: 'tag', }, webpackFinal: (config) => { config.resolve ??= {}; diff --git a/packages/web/.storybook/preview.ts b/packages/web/.storybook/preview.ts index 0124cbf1..32d93656 100644 --- a/packages/web/.storybook/preview.ts +++ b/packages/web/.storybook/preview.ts @@ -1,12 +1,12 @@ -import type { Preview } from "@storybook/react"; -import "../styles/globals.css"; +import type {Preview} from '@storybook/react'; +import '../styles/globals.css'; const preview: Preview = { parameters: { backgrounds: { - default: "light", + default: 'light', }, - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: {argTypesRegex: '^on[A-Z].*'}, controls: { matchers: { color: /(background|color)$/i, diff --git a/packages/web/Dockerfile b/packages/web/Dockerfile index 9291f7c3..dad564c4 100644 --- a/packages/web/Dockerfile +++ b/packages/web/Dockerfile @@ -13,7 +13,6 @@ COPY yarn.lock package.json ./ # Copy package.jsons of all related packages COPY packages/common/package.json ./packages/common/ -COPY packages/eslint-config/package.json ./packages/eslint-config/ COPY packages/web/package.json ./packages/web/ WORKDIR /app/packages/web diff --git a/packages/web/assets/index.ts b/packages/web/assets/index.ts index 9e42bc8a..14466159 100644 --- a/packages/web/assets/index.ts +++ b/packages/web/assets/index.ts @@ -1,5 +1,5 @@ -export { default as underConstructionPng } from "./underconstruction.png"; -export { default as underConstructionWebp } from "./underconstruction.webp"; -export { default as buddyProjectSvg } from "./buddyproject_logo.svg"; -export { default as yesbotBuddyProjectPng } from "./yesbot-buddy-project.png"; -export { default as yesbotBuddyProjectWebp } from "./yesbot-buddy-project.webp"; +export {default as underConstructionPng} from './underconstruction.png'; +export {default as underConstructionWebp} from './underconstruction.webp'; +export {default as buddyProjectSvg} from './buddyproject_logo.svg'; +export {default as yesbotBuddyProjectPng} from './yesbot-buddy-project.png'; +export {default as yesbotBuddyProjectWebp} from './yesbot-buddy-project.webp'; diff --git a/packages/web/codegen.ts b/packages/web/codegen.ts index fb51f39a..37f9139c 100644 --- a/packages/web/codegen.ts +++ b/packages/web/codegen.ts @@ -1,38 +1,38 @@ -import { CodegenConfig } from "@graphql-codegen/cli"; -import { readFile, writeFile } from "fs/promises"; -import { format } from "prettier"; +import {readFile, writeFile} from 'fs/promises'; +import {type CodegenConfig} from '@graphql-codegen/cli'; +import {format} from 'prettier'; const formatFile = async (path: string) => { - const content = await readFile(path, "utf-8"); - const formatted = await format(content, { parser: "typescript" }); - await writeFile(path, formatted, "utf-8"); + const content = await readFile(path, 'utf-8'); + const formatted = await format(content, {parser: 'typescript'}); + await writeFile(path, formatted, 'utf-8'); }; const config: CodegenConfig = { overwrite: true, schema: { - "http://localhost:5000/graphql": { + 'http://localhost:5000/graphql': { headers: { - Cookie: "ytf-cookie-consent=0;", + Cookie: 'ytf-cookie-consent=0;', }, }, }, - documents: "./**/*.graphql", + documents: './**/*.graphql', hooks: { afterAllFileWrite: async (...filePaths: string[]) => { await Promise.all(filePaths.map(formatFile)); }, }, generates: { - "src/__generated__/graphql.ts": { + 'src/__generated__/graphql.ts': { plugins: [ - "@atmina/only-enum-types", - "typescript-operations", - "typescript-graphql-request", + '@atmina/only-enum-types', + 'typescript-operations', + 'typescript-graphql-request', ], config: { scalars: { - DateTime: "string", + DateTime: 'string', }, enumsAsTypes: true, preResolveTypes: true, diff --git a/packages/web/cypress.config.ts b/packages/web/cypress.config.ts index f584bd05..c6b2a52b 100644 --- a/packages/web/cypress.config.ts +++ b/packages/web/cypress.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from "cypress"; -import setup from "./cypress/plugins"; +import {defineConfig} from 'cypress'; +import setup from './cypress/plugins'; export default defineConfig({ chromeWebSecurity: false, @@ -10,6 +10,6 @@ export default defineConfig({ setupNodeEvents(on, config) { setup(on, config); }, - baseUrl: "http://web:3000", + baseUrl: 'http://web:3000', }, }); diff --git a/packages/web/cypress/e2e/example.cy.ts b/packages/web/cypress/e2e/example.cy.ts index 1e153d9f..50b12965 100644 --- a/packages/web/cypress/e2e/example.cy.ts +++ b/packages/web/cypress/e2e/example.cy.ts @@ -1,7 +1,7 @@ -describe("Website", () => { - specify("displays without errors", () => { - cy.visit("/"); - cy.contains("Accept").click(); - cy.contains("We are happy to have you").should("exist"); +describe('Website', () => { + specify('displays without errors', () => { + cy.visit('/'); + cy.contains('Accept').click(); + cy.contains('We are happy to have you').should('exist'); }); }); diff --git a/packages/web/cypress/support/e2e.ts b/packages/web/cypress/support/e2e.ts index 413b0ecf..3d469a6b 100644 --- a/packages/web/cypress/support/e2e.ts +++ b/packages/web/cypress/support/e2e.ts @@ -14,4 +14,4 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import "./commands"; +import './commands'; diff --git a/packages/web/declaration.d.ts b/packages/web/declaration.d.ts index d4252c00..2c9f7a25 100644 --- a/packages/web/declaration.d.ts +++ b/packages/web/declaration.d.ts @@ -1,9 +1,9 @@ -declare module "*.css" { +declare module '*.css' { const mapping: Record; export default mapping; } -declare module "*.scss" { +declare module '*.scss' { const mapping: Record; export default mapping; } diff --git a/packages/web/environment.d.ts b/packages/web/environment.d.ts index 62a42649..3850bf1e 100644 --- a/packages/web/environment.d.ts +++ b/packages/web/environment.d.ts @@ -1,6 +1,6 @@ declare namespace NodeJS { export interface ProcessEnv { - NODE_ENV: "development" | "production" | "test"; + NODE_ENV: 'development' | 'production' | 'test'; SERVER_BACKEND_GRAPHQL_URL: string; FRONTEND_URL: string; } diff --git a/packages/web/eslint.config.js b/packages/web/eslint.config.js new file mode 100644 index 00000000..2b87df4d --- /dev/null +++ b/packages/web/eslint.config.js @@ -0,0 +1,15 @@ +const storybookPlugin = require('eslint-plugin-storybook'); + +const storybookRules = storybookPlugin.configs.recommended.overrides.map( + (conf) => ({...conf, plugins: {storybook: storybookPlugin}}), +); + +/** + * @type {import('eslint').Linter.FlatConfig[]} + */ +module.exports = [ + ...require('@atmina/linting/eslint/recommended'), + require('@atmina/linting/eslint/tailwind'), + require('@atmina/linting/eslint/next')(require('@next/eslint-plugin-next')), + ...storybookRules, +]; diff --git a/packages/web/graphql.config.yml b/packages/web/graphql.config.yml index ccb0dbc8..41c6ff65 100644 --- a/packages/web/graphql.config.yml +++ b/packages/web/graphql.config.yml @@ -1,9 +1,9 @@ schema: schema.graphql extensions: - endpoints: - Backend: - url: http://localhost:5000/graphql - headers: - user-agent: JS GraphQL - Cookie: ytf-cookie-consent=0; - introspect: false + endpoints: + Backend: + url: http://localhost:5000/graphql + headers: + user-agent: JS GraphQL + Cookie: ytf-cookie-consent=0; + introspect: false diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 4ace2b62..559cc0e6 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -1,4 +1,4 @@ -const { PHASE_DEVELOPMENT_SERVER } = require("next/constants"); +const {PHASE_DEVELOPMENT_SERVER} = require('next/constants'); /** @returns {import("next").NextConfig} */ const config = (phase) => ({ @@ -11,27 +11,27 @@ const config = (phase) => ({ }, rewrites: () => [ { - source: "/graphql", - destination: "http://localhost:5000/graphql", + source: '/graphql', + destination: 'http://localhost:5000/graphql', }, { - source: "/typesense/:slug*", - destination: "http://localhost:8108/:slug*", + source: '/typesense/:slug*', + destination: 'http://localhost:8108/:slug*', }, { - source: "/oauth/:slug*", - destination: "http://localhost:5000/oauth/:slug*", + source: '/oauth/:slug*', + destination: 'http://localhost:5000/oauth/:slug*', }, ], headers: () => [ { - source: "/:path*", + source: '/:path*', headers: [ - { key: "X-Frame-Options", value: "DENY" }, + {key: 'X-Frame-Options', value: 'DENY'}, { - key: "Content-Security-Policy", + key: 'Content-Security-Policy', value: `default-src 'self'; img-src 'self' https://cdn.discordapp.com/avatars/ data:; child-src 'none'; script-src 'self' 'unsafe-inline' ${ - phase === PHASE_DEVELOPMENT_SERVER ? "'unsafe-eval'" : "" + phase === PHASE_DEVELOPMENT_SERVER ? "'unsafe-eval'" : '' }; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'`, }, ], diff --git a/packages/web/package.json b/packages/web/package.json index c6070148..2dba3a5b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -9,13 +9,18 @@ "dev": "yarn codegen && yarn next", "e2e:open": "cypress open --config baseUrl=http://localhost:3000", "e2e:run": "cypress run --config baseUrl=http://localhost:3000", - "lint": "eslint \"./**/*.{js,jsx,ts,tsx}\" --max-warnings=0", + "lint": "yarn run lint:fix && yarn run prettier:fix", "start": "next start", "test": "echo web: ok", "storybook": "storybook dev -p 6006", - "storybook:build": "storybook build" + "storybook:build": "storybook build", + "eslint:check": "eslint . --report-unused-disable-directives --max-warnings 0", + "eslint:fix": "eslint . --fix --report-unused-disable-directives --max-warnings 0", + "prettier:check": "prettier . --check", + "prettier:fix": "prettier . --write" }, "devDependencies": { + "@atmina/linting": "^2.0.1", "@atmina/only-enum-types": "0.7.0", "@graphql-codegen/cli": "5.0.0", "@graphql-codegen/near-operation-file-preset": "3.0.0", @@ -29,16 +34,17 @@ "@storybook/react": "7.6.3", "@storybook/testing-library": "0.2.2", "@types/cookie": "0.6.0", + "@types/eslint": "^8.44.8", "@types/prettier": "^3.0.0", - "@yestheory.family/eslint-config": "^1.0.0", "autoprefixer": "^10.4.16", "chromatic": "^9.1.0", "cypress": "11.2.0", "env-cmd": "^10.1.0", - "eslint": "8.55.0", + "eslint": "^8.55.0", "eslint-config-next": "^14.0.3", "eslint-plugin-storybook": "^0.6.15", "postcss": "^8.4.32", + "prettier": "^3.1.0", "storybook": "7.6.3", "tailwindcss": "^3.3.5", "tsconfig-paths-webpack-plugin": "^4.1.0", @@ -64,5 +70,6 @@ "tailwind-merge": "^2.0.0", "typesense": "^1.7.2" }, - "license": "MIT" + "license": "MIT", + "prettier": "@atmina/linting/prettier" } diff --git a/packages/web/src/__generated__/graphql.ts b/packages/web/src/__generated__/graphql.ts index 1189d167..b9c197b3 100644 --- a/packages/web/src/__generated__/graphql.ts +++ b/packages/web/src/__generated__/graphql.ts @@ -1,9 +1,9 @@ -import { GraphQLClient } from "graphql-request"; -import { GraphQLClientRequestHeaders } from "graphql-request/build/cjs/types"; -import gql from "graphql-tag"; +import {GraphQLClient} from 'graphql-request'; +import {GraphQLClientRequestHeaders} from 'graphql-request/build/cjs/types'; +import gql from 'graphql-tag'; export type Maybe = T | null; export type InputMaybe = Maybe; -export type Exact = { +export type Exact = { [K in keyof T]: T[K]; }; export type MakeOptional = Omit & { @@ -12,36 +12,35 @@ export type MakeOptional = Omit & { export type MakeMaybe = Omit & { [SubKey in K]: Maybe; }; -export type MakeEmpty< - T extends { [key: string]: unknown }, - K extends keyof T, -> = { [_ in K]?: never }; +export type MakeEmpty = { + [_ in K]?: never; +}; export type Incremental = | T | { - [P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never; + [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never; }; -export type BuddyProjectStatus = "MATCHED" | "NOT_SIGNED_UP" | "SIGNED_UP"; +export type BuddyProjectStatus = 'MATCHED' | 'NOT_SIGNED_UP' | 'SIGNED_UP'; -export type SignUpResult = "FAILURE" | "FULL_SUCCESS" | "SUCCESS_DMS_CLOSED"; +export type SignUpResult = 'FAILURE' | 'FULL_SUCCESS' | 'SUCCESS_DMS_CLOSED'; -export type ServerStateQueryVariables = Exact<{ [key: string]: never }>; +export type ServerStateQueryVariables = Exact<{[key: string]: never}>; export type ServerStateQuery = { - __typename?: "Query"; - me?: { __typename?: "AuthenticatedUser"; isOnServer: boolean } | null; + __typename?: 'Query'; + me?: {__typename?: 'AuthenticatedUser'; isOnServer: boolean} | null; }; -export type BuddyProjectStateQueryVariables = Exact<{ [key: string]: never }>; +export type BuddyProjectStateQueryVariables = Exact<{[key: string]: never}>; export type BuddyProjectStateQuery = { - __typename?: "Query"; + __typename?: 'Query'; getBuddyProjectStatus: { - __typename?: "BuddyProjectStatusPayload"; + __typename?: 'BuddyProjectStatusPayload'; status: BuddyProjectStatus; buddy?: { - __typename?: "BuddyProjectEntry"; + __typename?: 'BuddyProjectEntry'; userId: string; username?: string | null; } | null; @@ -53,30 +52,30 @@ export type BuddyProjectSignUpMutationVariables = Exact<{ }>; export type BuddyProjectSignUpMutation = { - __typename?: "Mutation"; + __typename?: 'Mutation'; buddyProjectSignUp: { - __typename?: "WebSignUpResult"; + __typename?: 'WebSignUpResult'; result: SignUpResult; status: { - __typename?: "BuddyProjectStatusPayload"; + __typename?: 'BuddyProjectStatusPayload'; status: BuddyProjectStatus; }; }; }; -export type TypesenseApiKeyQueryVariables = Exact<{ [key: string]: never }>; +export type TypesenseApiKeyQueryVariables = Exact<{[key: string]: never}>; export type TypesenseApiKeyQuery = { - __typename?: "Query"; + __typename?: 'Query'; groupchatSearchToken: string; }; -export type CurrentUserQueryVariables = Exact<{ [key: string]: never }>; +export type CurrentUserQueryVariables = Exact<{[key: string]: never}>; export type CurrentUserQuery = { - __typename?: "Query"; + __typename?: 'Query'; me?: { - __typename: "AuthenticatedUser"; + __typename: 'AuthenticatedUser'; id: string; username: string; avatarUrl?: string | null; @@ -84,9 +83,9 @@ export type CurrentUserQuery = { } | null; }; -export type LogoutMutationVariables = Exact<{ [key: string]: never }>; +export type LogoutMutationVariables = Exact<{[key: string]: never}>; -export type LogoutMutation = { __typename?: "Mutation"; logout: boolean }; +export type LogoutMutation = {__typename?: 'Mutation'; logout: boolean}; export const ServerStateDocument = gql` query ServerState { @@ -165,8 +164,8 @@ export function getSdk( ...requestHeaders, ...wrappedRequestHeaders, }), - "ServerState", - "query", + 'ServerState', + 'query', ); }, BuddyProjectState( @@ -178,10 +177,10 @@ export function getSdk( client.request( BuddyProjectStateDocument, variables, - { ...requestHeaders, ...wrappedRequestHeaders }, + {...requestHeaders, ...wrappedRequestHeaders}, ), - "BuddyProjectState", - "query", + 'BuddyProjectState', + 'query', ); }, BuddyProjectSignUp( @@ -193,10 +192,10 @@ export function getSdk( client.request( BuddyProjectSignUpDocument, variables, - { ...requestHeaders, ...wrappedRequestHeaders }, + {...requestHeaders, ...wrappedRequestHeaders}, ), - "BuddyProjectSignUp", - "mutation", + 'BuddyProjectSignUp', + 'mutation', ); }, TypesenseApiKey( @@ -208,10 +207,10 @@ export function getSdk( client.request( TypesenseApiKeyDocument, variables, - { ...requestHeaders, ...wrappedRequestHeaders }, + {...requestHeaders, ...wrappedRequestHeaders}, ), - "TypesenseApiKey", - "query", + 'TypesenseApiKey', + 'query', ); }, CurrentUser( @@ -224,8 +223,8 @@ export function getSdk( ...requestHeaders, ...wrappedRequestHeaders, }), - "CurrentUser", - "query", + 'CurrentUser', + 'query', ); }, Logout( @@ -238,8 +237,8 @@ export function getSdk( ...requestHeaders, ...wrappedRequestHeaders, }), - "Logout", - "mutation", + 'Logout', + 'mutation', ); }, }; diff --git a/packages/web/src/app/buddyproject/actions/signup-server-action.ts b/packages/web/src/app/buddyproject/actions/signup-server-action.ts index 025522c3..25b730c1 100644 --- a/packages/web/src/app/buddyproject/actions/signup-server-action.ts +++ b/packages/web/src/app/buddyproject/actions/signup-server-action.ts @@ -1,6 +1,6 @@ -"use server"; +'use server'; -import { graphqlWithHeaders } from "../../../lib/graphql/client"; +import {graphqlWithHeaders} from '../../../lib/graphql/client'; export const buddyProjectSignUp = async () => { const result = await graphqlWithHeaders((sdk) => sdk.BuddyProjectSignUp()); diff --git a/packages/web/src/app/buddyproject/components/buddy-project-button.tsx b/packages/web/src/app/buddyproject/components/buddy-project-button.tsx index 6b851452..24ec8213 100644 --- a/packages/web/src/app/buddyproject/components/buddy-project-button.tsx +++ b/packages/web/src/app/buddyproject/components/buddy-project-button.tsx @@ -1,14 +1,14 @@ -"use client"; - -import NiceModal from "@ebay/nice-modal-react"; -import { useRouter } from "next/navigation"; -import { FC, useCallback } from "react"; -import { Button } from "ui"; -import { type BuddyProjectStatus } from "../../../__generated__/graphql"; -import { ServerJoinConfirmationModal } from "./server-join-confirmation"; -import { SignupSuccessModal } from "./signup-success-modal"; -import { buddyProjectSignUp } from "../actions/signup-server-action"; -import { navigateToLogin } from "../../../context/user/navigate-to-login"; +'use client'; + +import NiceModal from '@ebay/nice-modal-react'; +import {useRouter} from 'next/navigation'; +import {type FC, useCallback} from 'react'; +import {Button} from 'ui'; +import {type BuddyProjectStatus} from '../../../__generated__/graphql'; +import {navigateToLogin} from '../../../context/user/navigate-to-login'; +import {buddyProjectSignUp} from '../actions/signup-server-action'; +import {ServerJoinConfirmationModal} from './server-join-confirmation'; +import {SignupSuccessModal} from './signup-success-modal'; export type BuddyProjectButtonProps = { isLoggedIn: boolean; @@ -35,11 +35,11 @@ export const BuddyProjectButton: FC = ({ const signUpResult = await buddyProjectSignUp(); if ( - signUpResult === "FULL_SUCCESS" || - signUpResult === "SUCCESS_DMS_CLOSED" + signUpResult === 'FULL_SUCCESS' || + signUpResult === 'SUCCESS_DMS_CLOSED' ) { void NiceModal.show(SignupSuccessModal, { - hasDmsClosed: signUpResult === "SUCCESS_DMS_CLOSED", + hasDmsClosed: signUpResult === 'SUCCESS_DMS_CLOSED', }); } @@ -50,7 +50,7 @@ export const BuddyProjectButton: FC = ({ return ; } - if (state === "MATCHED" || state === "SIGNED_UP") { + if (state === 'MATCHED' || state === 'SIGNED_UP') { return ; } diff --git a/packages/web/src/app/buddyproject/components/server-join-confirmation.tsx b/packages/web/src/app/buddyproject/components/server-join-confirmation.tsx index 0a177439..33f67033 100644 --- a/packages/web/src/app/buddyproject/components/server-join-confirmation.tsx +++ b/packages/web/src/app/buddyproject/components/server-join-confirmation.tsx @@ -1,7 +1,7 @@ -"use client"; +'use client'; -import NiceModal, { useModal } from "@ebay/nice-modal-react"; -import { Modal, ModalActionButton } from "ui/client"; +import NiceModal, {useModal} from '@ebay/nice-modal-react'; +import {Modal, type ModalActionButton} from 'ui/client'; export const ServerJoinConfirmationModal = NiceModal.create(() => { const modal = useModal(); @@ -18,14 +18,14 @@ export const ServerJoinConfirmationModal = NiceModal.create(() => { const actions = [ { - text: "No", + text: 'No', onClick: cancel, }, - { text: "Yes", onClick: confirm }, + {text: 'Yes', onClick: confirm}, ] satisfies ModalActionButton[]; return ( - +

To join the Buddy project, you have to join the Yes Fam Discord server. Continue? diff --git a/packages/web/src/app/buddyproject/components/signup-success-modal.tsx b/packages/web/src/app/buddyproject/components/signup-success-modal.tsx index 960121d9..536e2da7 100644 --- a/packages/web/src/app/buddyproject/components/signup-success-modal.tsx +++ b/packages/web/src/app/buddyproject/components/signup-success-modal.tsx @@ -1,24 +1,24 @@ -"use client"; +'use client'; -import NiceModal, { useModal } from "@ebay/nice-modal-react"; -import { Modal, ModalActionButton } from "ui/client"; +import NiceModal, {useModal} from '@ebay/nice-modal-react'; +import {Modal, type ModalActionButton} from 'ui/client'; -export const SignupSuccessModal = NiceModal.create<{ hasDmsClosed: boolean }>( - ({ hasDmsClosed }) => { +export const SignupSuccessModal = NiceModal.create<{hasDmsClosed: boolean}>( + ({hasDmsClosed}) => { const modal = useModal(); const close = () => modal.remove(); const actions: ModalActionButton[] = [ { - text: "Close", - className: "block ml-auto", + text: 'Close', + className: 'block ml-auto', onClick: close, }, ]; return ( - +

You are now signed up to the Buddy Project! YesBot will message you soon with your buddy and the questions. Until then, feel free to @@ -28,7 +28,7 @@ export const SignupSuccessModal = NiceModal.create<{ hasDmsClosed: boolean }>(
{hasDmsClosed && ( -

+

Note: YesBot was not able to send you a confirmation message because of your Privacy Settings. Make sure to allow DMs from server members to be able to receive information about your buddy. diff --git a/packages/web/src/app/buddyproject/page.tsx b/packages/web/src/app/buddyproject/page.tsx index d0a47520..76374934 100644 --- a/packages/web/src/app/buddyproject/page.tsx +++ b/packages/web/src/app/buddyproject/page.tsx @@ -1,40 +1,40 @@ -import { Metadata } from "next"; -import { Image } from "ui"; -import { ScrollToActionContainer } from "ui/client"; -import { InfoGrid } from "ui/buddyproject"; -import { buddyProjectSvg, yesbotBuddyProjectWebp } from "../../../assets"; -import { BuddyProjectButton } from "./components/buddy-project-button"; -import { graphqlWithHeaders } from "../../lib/graphql/client"; +import {type Metadata} from 'next'; +import {Image} from 'ui'; +import {InfoGrid} from 'ui/buddyproject'; +import {ScrollToActionContainer} from 'ui/client'; +import {buddyProjectSvg, yesbotBuddyProjectWebp} from '../../../assets'; +import {graphqlWithHeaders} from '../../lib/graphql/client'; +import {BuddyProjectButton} from './components/buddy-project-button'; -const title = "The Buddy Project"; +const title = 'The Buddy Project'; const description = - "The Buddy Project is a recurring event led by the Yes Theory community to make new friends!"; + 'The Buddy Project is a recurring event led by the Yes Theory community to make new friends!'; export const metadata: Metadata = { title, description, openGraph: { title, - type: "website", + type: 'website', images: yesbotBuddyProjectWebp.src, - url: "https://yestheory.family/buddyproject", + url: 'https://yestheory.family/buddyproject', description, }, }; const CTA = () => { return ( - -

+ +
{"Buddy -

+

Great things come to those who are willing to risk rejection and put themselves out there.

@@ -50,19 +50,19 @@ const Page = async () => { const isLoggedIn = !!currentUser; const isOnServer = currentUser?.isOnServer ?? false; - const emptyState = { status: "NOT_SIGNED_UP", buddy: null } as const; + const emptyState = {status: 'NOT_SIGNED_UP', buddy: null} as const; const state = isLoggedIn ? (await graphqlWithHeaders((sdk) => sdk.BuddyProjectState())) .getBuddyProjectStatus ?? emptyState : emptyState; - const { status, buddy } = state; + const {status, buddy} = state; return ( <> -
- +
+ = ({ - isLoggedIn, -}) => { +export const GroupChatSearch: FC<{isLoggedIn: boolean}> = ({isLoggedIn}) => { const [search, setSearch] = useState<{ query: string; platforms: GroupChatPlatform[]; - }>({ query: "", platforms: [] }); + }>({query: '', platforms: []}); - const { loading, groupchats } = useGroupchatSearch( + const {loading, groupchats} = useGroupchatSearch( search.query, search.platforms, ); return ( -
+
- setSearch({ query, platforms }) - } + onSearchChange={({query, platforms}) => setSearch({query, platforms})} /> -
+

Not seeing what you are looking for?

- - Ask the group's admin to shoot me a message{" "} + + Ask the group's admin to shoot me a message{' '} over on Discord - {" "} + {' '} to get the group on here!

{!isLoggedIn && (

- - Only Facebook groups and Instagram pages are available without{" "} + + Only Facebook groups and Instagram pages are available without{' '} logging in with Discord @@ -71,11 +63,11 @@ export const GroupChatSearch: FC<{ isLoggedIn: boolean }> = ({ {groupchats.map((r) => ( -

+
))} - {loading && "Loading..."} + {loading && 'Loading...'}
); diff --git a/packages/web/src/app/groupchats/components/use-groupchat-search.ts b/packages/web/src/app/groupchats/components/use-groupchat-search.ts index 4e922ddf..26896927 100644 --- a/packages/web/src/app/groupchats/components/use-groupchat-search.ts +++ b/packages/web/src/app/groupchats/components/use-groupchat-search.ts @@ -1,7 +1,7 @@ -import { useState, useEffect, useCallback } from "react"; -import { GroupChatPlatform } from "../../../ui/groupchats"; -import { SearchClient } from "typesense"; -import { useTypesense } from "../../../context/typesense"; +import {useState, useEffect, useCallback} from 'react'; +import {type SearchClient} from 'typesense'; +import {useTypesense} from '../../../context/typesense'; +import {type GroupChatPlatform} from '../../../ui/groupchats'; export type GroupchatResult = { id: string; @@ -22,21 +22,21 @@ const fetchResults = async ( page = 1, ): Promise<[GroupchatResult[], hasNext: boolean]> => { const filterBy = - platforms.length === 0 ? "" : `platform:[${platforms.join(",")}]`; + platforms.length === 0 ? '' : `platform:[${platforms.join(',')}]`; const { hits, found, page: returnedPage, } = await searchClient - .collections("groupchats") + .collections('groupchats') .documents() .search( { q: queryText, - query_by: "name,keywords,description", + query_by: 'name,keywords,description', filter_by: filterBy, - sort_by: "promoted:desc", + sort_by: 'promoted:desc', per_page: pageSize, page, }, @@ -46,7 +46,7 @@ const fetchResults = async ( const results = hits ?.map((h) => h.document) - .filter((x): x is GroupchatResult => "name" in x) ?? []; + .filter((x): x is GroupchatResult => 'name' in x) ?? []; const hasNext = returnedPage * pageSize < found; @@ -62,7 +62,7 @@ export const useGroupchatSearch = ( queryText: string, platforms: GroupChatPlatform[], ): UseGroupchatSearchReturn => { - const { client } = useTypesense(); + const {client} = useTypesense(); const [nextPage, setNextPage] = useState(1); const [hasNextPage, setHasNextPage] = useState(true); @@ -70,7 +70,7 @@ export const useGroupchatSearch = ( const [chats, setChats] = useState([]); const fetchMore = useCallback( - async (mode: "append" | "replace") => { + async (mode: 'append' | 'replace') => { if (loading || !hasNextPage) return; setLoading(true); @@ -82,7 +82,7 @@ export const useGroupchatSearch = ( ); setChats((chats) => - mode === "replace" ? newChats : [...chats, ...newChats], + mode === 'replace' ? newChats : [...chats, ...newChats], ); setHasNextPage(hasNext); if (hasNext) setNextPage(nextPage + 1); @@ -93,7 +93,7 @@ export const useGroupchatSearch = ( useEffect(() => { setNextPage(1); - void fetchMore("replace"); + void fetchMore('replace'); }, [queryText, platforms, client]); useEffect(() => { @@ -105,21 +105,21 @@ export const useGroupchatSearch = ( const scrollTarget = document.scrollingElement as HTMLHtmlElement | null; if (!scrollTarget || tripped) return; - const footerHeight = document.querySelector("footer")?.clientHeight ?? 0; + const footerHeight = document.querySelector('footer')?.clientHeight ?? 0; const loadingOffset = footerHeight + 150; - const { scrollTop, scrollHeight, clientHeight } = scrollTarget; + const {scrollTop, scrollHeight, clientHeight} = scrollTarget; if (scrollTop + clientHeight + loadingOffset >= scrollHeight) { console.log(`Scroll triggered for nextPage ${nextPage}`); tripped = true; - void fetchMore("append"); + void fetchMore('append'); } }; - document.addEventListener("scroll", scrollListener); + document.addEventListener('scroll', scrollListener); - return () => document.removeEventListener("scroll", scrollListener); + return () => document.removeEventListener('scroll', scrollListener); }, [fetchMore, loading, nextPage]); return { diff --git a/packages/web/src/app/groupchats/page.tsx b/packages/web/src/app/groupchats/page.tsx index 062bb954..e501eed3 100644 --- a/packages/web/src/app/groupchats/page.tsx +++ b/packages/web/src/app/groupchats/page.tsx @@ -1,12 +1,12 @@ -import { Heading } from "ui"; -import { GroupChatSearch } from "./components/group-chat-search"; -import { Metadata } from "next"; -import { getIsLoggedIn } from "../../context/user/user"; -import { TypesenseProvider } from "../../context/typesense"; -import { graphqlWithHeaders } from "../../lib/graphql/client"; +import {type Metadata} from 'next'; +import {Heading} from 'ui'; +import {TypesenseProvider} from '../../context/typesense'; +import {getIsLoggedIn} from '../../context/user/user'; +import {graphqlWithHeaders} from '../../lib/graphql/client'; +import {GroupChatSearch} from './components/group-chat-search'; export const metadata: Metadata = { - title: "Groupchats", + title: 'Groupchats', }; const GroupChats = async () => { @@ -14,8 +14,8 @@ const GroupChats = async () => { const apiKey = await graphqlWithHeaders((sdk) => sdk.TypesenseApiKey()); return ( -
- +
+ diff --git a/packages/web/src/app/layout.tsx b/packages/web/src/app/layout.tsx index f1122a9f..a29ca5b4 100644 --- a/packages/web/src/app/layout.tsx +++ b/packages/web/src/app/layout.tsx @@ -1,65 +1,65 @@ -import { Footer } from "ui"; -import { PropsWithChildren, Suspense } from "react"; -import { Metadata } from "next"; -import { CookieConsent } from "../components/cookie-consent/cookie-consent"; -import { Nav } from "./nav"; -import { Providers } from "./providers"; +import {type Metadata} from 'next'; +import {type PropsWithChildren, Suspense} from 'react'; +import {Footer} from 'ui'; +import {CookieConsent} from '../components/cookie-consent/cookie-consent'; +import {Nav} from './nav'; +import {Providers} from './providers'; -import "../../styles/globals.css"; -import { getCurrentUser } from "../context/user/user"; +import '../../styles/globals.css'; +import {getCurrentUser} from '../context/user/user'; export const metadata: Metadata = { metadataBase: new URL(process.env.FRONTEND_URL), title: { - absolute: "YesTheory Family", - template: "%s - YesTheory Family", + absolute: 'YesTheory Family', + template: '%s - YesTheory Family', }, icons: { icon: [ { - url: "/favicon-32x32.png", - sizes: "32x32", - type: "image/png", + url: '/favicon-32x32.png', + sizes: '32x32', + type: 'image/png', }, { - url: "/favicon-16x16.png", - sizes: "16x16", - type: "image/png", + url: '/favicon-16x16.png', + sizes: '16x16', + type: 'image/png', }, ], apple: { - sizes: "180x180", - url: "/apple-touch-icon.png", + sizes: '180x180', + url: '/apple-touch-icon.png', }, }, }; -const RootLayout = async ({ children }: PropsWithChildren) => { +const RootLayout = async ({children}: PropsWithChildren) => { const user = await getCurrentUser(); return ( - + - - - - + + + + -
+