├── .prettierrc ├── packages ├── client │ ├── src │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useApp.ts │ │ ├── client.d.ts │ │ ├── components │ │ │ ├── molecules │ │ │ │ ├── index.ts │ │ │ │ └── Field │ │ │ │ │ ├── index.ts │ │ │ │ │ └── Field.tsx │ │ │ ├── organisms │ │ │ │ ├── index.ts │ │ │ │ └── LoginForm │ │ │ │ │ ├── index.ts │ │ │ │ │ └── LoginForm.tsx │ │ │ ├── atoms │ │ │ │ ├── Button │ │ │ │ │ ├── index.ts │ │ │ │ │ └── Button.tsx │ │ │ │ ├── Input │ │ │ │ │ ├── index.ts │ │ │ │ │ └── Input.tsx │ │ │ │ ├── ErrorLine │ │ │ │ │ ├── index.ts │ │ │ │ │ └── ErrorLine.tsx │ │ │ │ └── index.ts │ │ │ ├── pages │ │ │ │ ├── LoginPage │ │ │ │ │ ├── index.ts │ │ │ │ │ └── LoginPage.tsx │ │ │ │ ├── Game.tsx │ │ │ │ ├── Error.tsx │ │ │ │ ├── Forum.tsx │ │ │ │ ├── Main.tsx │ │ │ │ ├── Profile.tsx │ │ │ │ ├── SignIn.tsx │ │ │ │ ├── SignUp.tsx │ │ │ │ ├── ForumTopic.tsx │ │ │ │ ├── LeaderBoard.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── App.tsx │ │ │ └── App.test.tsx │ │ ├── vite-env.d.ts │ │ ├── index.css │ │ ├── services │ │ │ ├── api │ │ │ │ └── index.ts │ │ │ └── helpers │ │ │ │ └── ErrorBoundary.tsx │ │ ├── main.tsx │ │ ├── store │ │ │ └── configure.ts │ │ └── router │ │ │ └── index.tsx │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── tsconfig.node.json │ ├── jest.config.js │ ├── nginx.conf │ ├── .gitignore │ ├── index.html │ ├── vite.config.ts │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg └── server │ ├── jest.config.js │ ├── tsconfig.prod.json │ ├── __tests__ │ └── example.test.ts │ ├── index.ts │ ├── db.ts │ ├── tsconfig.json │ └── package.json ├── .gitignore ├── docs ├── image.png ├── scenario │ ├── matrix.png │ └── subsequence.png ├── README.md ├── workFlow.md └── scenario.md ├── .editorconfig ├── .github ├── pull_request_template.md └── workflows │ └── checks.yml ├── init.js ├── .env.sample ├── lerna.json ├── lefthook.yml ├── .eslintrc.js ├── Dockerfile.client ├── Dockerfile.server ├── package.json ├── docker-compose.yml └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "width": 50 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useApp"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | .idea 4 | .vscode 5 | dist 6 | tmp -------------------------------------------------------------------------------- /packages/client/src/client.d.ts: -------------------------------------------------------------------------------- 1 | declare const __SERVER_PORT__: number; 2 | -------------------------------------------------------------------------------- /packages/client/src/components/molecules/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Field"; 2 | -------------------------------------------------------------------------------- /packages/client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrzenxxx/cybreach/HEAD/docs/image.png -------------------------------------------------------------------------------- /packages/client/src/components/organisms/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./LoginForm"; 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,ts}] 4 | indent_style = space 5 | indent_size = 2 -------------------------------------------------------------------------------- /docs/scenario/matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrzenxxx/cybreach/HEAD/docs/scenario/matrix.png -------------------------------------------------------------------------------- /packages/client/src/components/atoms/Button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from "./Button"; 2 | -------------------------------------------------------------------------------- /packages/client/src/components/atoms/Input/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Input } from "./Input"; 2 | -------------------------------------------------------------------------------- /packages/client/src/components/molecules/Field/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Field } from "./Field"; 2 | -------------------------------------------------------------------------------- /packages/client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /docs/scenario/subsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrzenxxx/cybreach/HEAD/docs/scenario/subsequence.png -------------------------------------------------------------------------------- /packages/client/src/components/atoms/ErrorLine/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ErrorLine } from "./ErrorLine"; 2 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/LoginPage/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LoginPage } from "./LoginPage"; 2 | -------------------------------------------------------------------------------- /packages/client/src/components/organisms/LoginForm/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LoginForm } from "./LoginForm"; 2 | -------------------------------------------------------------------------------- /packages/client/src/services/api/index.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/diegohaz/arc/blob/redux/src/services/api/index.js 2 | -------------------------------------------------------------------------------- /packages/server/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | }; 5 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/Game.tsx: -------------------------------------------------------------------------------- 1 | export function Game(): JSX.Element { 2 | return
Game
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/Error.tsx: -------------------------------------------------------------------------------- 1 | export function Error(): JSX.Element { 2 | return
404
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/Forum.tsx: -------------------------------------------------------------------------------- 1 | export function Forum(): JSX.Element { 2 | return
Forum
; 3 | } 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Какую задачу решаем 2 | 3 | 4 | ### Скриншоты/видяшка (если есть) 5 | 6 | ### TBD (если есть) -------------------------------------------------------------------------------- /packages/client/src/components/pages/Main.tsx: -------------------------------------------------------------------------------- 1 | export default function Main(): JSX.Element { 2 | return
Main
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/Profile.tsx: -------------------------------------------------------------------------------- 1 | export function Profile(): JSX.Element { 2 | return
Profile
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/SignIn.tsx: -------------------------------------------------------------------------------- 1 | export function SignIn(): JSX.Element { 2 | return
SignIn
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/SignUp.tsx: -------------------------------------------------------------------------------- 1 | export function SignUp(): JSX.Element { 2 | return
SignUp
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/client/src/components/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Button"; 2 | export * from "./ErrorLine"; 3 | export * from "./Input"; 4 | -------------------------------------------------------------------------------- /init.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | fs.copyFileSync('.env.sample', '.env') 4 | 5 | fs.mkdirSync('tmp/pgdata', { recursive: true }) 6 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/ForumTopic.tsx: -------------------------------------------------------------------------------- 1 | export function ForumTopic(): JSX.Element { 2 | return
ForumTopic
; 3 | } 4 | -------------------------------------------------------------------------------- /packages/server/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["**/*.test.ts", "**/*.mock.ts", "**/__test__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/LeaderBoard.tsx: -------------------------------------------------------------------------------- 1 | export function LeaderBoard(): JSX.Element { 2 | return
LeaderBoard
; 3 | } 4 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | CLIENT_PORT=3000 2 | SERVER_PORT=3001 3 | POSTGRES_USER=postgres 4 | POSTGRES_PASSWORD=postgres 5 | POSTGRES_DB=postgres 6 | POSTGRES_PORT=5432 7 | -------------------------------------------------------------------------------- /packages/client/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./atoms"; 2 | export * from "./molecules"; 3 | export * from "./organisms"; 4 | export * from "./pages"; 5 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Документация 2 | 3 | - [Сценарий игры](scenario.md) - Разработка сценария игры 4 | - [Flow работы с проектом](workFlow.md) - Описания порядка работы с проектом -------------------------------------------------------------------------------- /packages/client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "useNx": true, 4 | "npmClient": "yarn", 5 | "useWorkspaces": true, 6 | "version": "0.0.0" 7 | } 8 | -------------------------------------------------------------------------------- /packages/client/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import { RouterProvider } from "react-router-dom"; 2 | import { router } from "../router"; 3 | 4 | function App() { 5 | return ; 6 | } 7 | 8 | export default App; 9 | -------------------------------------------------------------------------------- /packages/client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | glob: '*.{ts,tsx}' 6 | run: yarn eslint {staged_files} 7 | prettier: 8 | glob: '*.{ts,tsx,css}' 9 | run: yarn prettier -w {staged_files} 10 | stage_fixed: true 11 | -------------------------------------------------------------------------------- /packages/client/jest.config.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | dotenv.config(); 3 | 4 | export default { 5 | preset: "ts-jest", 6 | testEnvironment: "jsdom", 7 | testMatch: ["/src/**/*.test.{ts,tsx}"], 8 | globals: { 9 | __SERVER_PORT__: process.env.SERVER_PORT, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/client/src/components/atoms/ErrorLine/ErrorLine.tsx: -------------------------------------------------------------------------------- 1 | interface ErrorLineProps { 2 | error: string | null; 3 | } 4 | 5 | export default function ErrorLine(props: ErrorLineProps) { 6 | const { error } = props; 7 | 8 | return {error}; 9 | } 10 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/LoginPage/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import { LoginForm } from "@/components"; 2 | 3 | export default function LoginPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /packages/server/__tests__/example.test.ts: -------------------------------------------------------------------------------- 1 | const magic = "🪄"; 2 | 3 | const cast = (spell: string, item: any) => { 4 | if (spell.startsWith(magic)) { 5 | return "🐷"; 6 | } 7 | 8 | return item; 9 | }; 10 | 11 | test("spell casting", () => { 12 | const result = cast(magic, "🐸"); 13 | expect(result).toBe("🐷"); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/client/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | } 3 | 4 | http { 5 | include mime.types; 6 | server { 7 | listen 80; 8 | listen [::]:80; 9 | 10 | location / { 11 | root /app; 12 | try_files $uri /index.html; 13 | add_header Access-Control-Allow-Origin *; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /packages/client/src/components/App.test.tsx: -------------------------------------------------------------------------------- 1 | import App from "./App"; 2 | import { render, screen } from "@testing-library/react"; 3 | 4 | // @ts-ignore 5 | global.fetch = jest.fn(() => 6 | Promise.resolve({ json: () => Promise.resolve("hey") }) 7 | ); 8 | 9 | test("Example test", async () => { 10 | render(); 11 | expect(screen).toBeDefined(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/client/src/components/pages/index.ts: -------------------------------------------------------------------------------- 1 | export { LeaderBoard } from "./LeaderBoard"; 2 | export { Forum } from "./Forum"; 3 | export { ForumTopic } from "./ForumTopic"; 4 | export { SignIn } from "./SignIn"; 5 | export { SignUp } from "./SignUp"; 6 | export { Profile } from "./Profile"; 7 | export { Game } from "./Game"; 8 | export { Error } from "./Error"; 9 | 10 | export * from "./LoginPage"; 11 | -------------------------------------------------------------------------------- /packages/client/src/components/atoms/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from "react"; 2 | 3 | interface ButtonProps extends ButtonHTMLAttributes { 4 | label?: string; 5 | } 6 | 7 | export default function Button({ label, ...props }: ButtonProps) { 8 | return ( 9 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/client/src/hooks/useApp.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | import type { RootState, AppDispatch } from "@/store/configure"; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | const useAppDispatch: () => AppDispatch = useDispatch; 6 | const useAppSelector: TypedUseSelectorHook = useSelector; 7 | 8 | export { useAppDispatch, useAppSelector }; 9 | -------------------------------------------------------------------------------- /packages/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {TODO: Set title :) } 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier', 11 | ], 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaVersion: 11, 15 | }, 16 | plugins: ['@typescript-eslint'], 17 | rules: { 18 | '@typescript-eslint/ban-ts-comment': 1, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "@/components/App"; 4 | import "./index.css"; 5 | import { Provider } from "react-redux"; 6 | import store from "./store/configure"; 7 | 8 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /packages/server/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import cors from "cors"; 3 | dotenv.config(); 4 | 5 | import express from "express"; 6 | import { createClientAndConnect } from "./db"; 7 | 8 | const app = express(); 9 | app.use(cors()); 10 | const port = Number(process.env.SERVER_PORT) || 3001; 11 | 12 | createClientAndConnect(); 13 | 14 | app.get("/", (_, res) => { 15 | res.json("👋 Howdy from the server :)"); 16 | }); 17 | 18 | app.listen(port, () => { 19 | console.log(` ➜ 🎸 Server is listening on port: ${port}`); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/client/src/components/atoms/Input/Input.tsx: -------------------------------------------------------------------------------- 1 | import { InputHTMLAttributes, FormEventHandler } from "react"; 2 | 3 | interface InputProps extends InputHTMLAttributes { 4 | handleInput?: FormEventHandler; 5 | } 6 | 7 | export default function Input(props: InputProps) { 8 | const { handleInput } = props; 9 | 10 | return ( 11 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import react from "@vitejs/plugin-react"; 4 | import dotenv from "dotenv"; 5 | dotenv.config(); 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | server: { 10 | port: Number(process.env.CLIENT_PORT) || 3000, 11 | }, 12 | define: { 13 | __SERVER_PORT__: process.env.SERVER_PORT, 14 | }, 15 | plugins: [react()], 16 | resolve: { 17 | alias: { 18 | "@": resolve("./src"), 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/client/src/store/configure.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/diegohaz/arc/wiki/Redux-modules 2 | import { configureStore } from "@reduxjs/toolkit"; 3 | 4 | const store = configureStore({ 5 | reducer: {}, 6 | }); 7 | 8 | // Infer the `RootState` and `AppDispatch` types from the store itself 9 | type RootState = ReturnType; 10 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 11 | type AppDispatch = typeof store.dispatch; 12 | 13 | export default store; 14 | 15 | export type { RootState, AppDispatch }; 16 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - dev 9 | 10 | jobs: 11 | eslint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - name: Install main deps 19 | run: yarn 20 | - name: Initialize 21 | run: yarn lerna bootstrap 22 | - name: Lint 23 | run: yarn lint 24 | - name: Test 25 | run: yarn test 26 | -------------------------------------------------------------------------------- /Dockerfile.client: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=16 2 | ARG CLIENT_PORT=3001 3 | 4 | FROM node:$NODE_VERSION-buster as base 5 | 6 | WORKDIR /app 7 | 8 | FROM base as builder 9 | 10 | COPY package.json yarn.lock ./ 11 | RUN yarn install --frozen-lockfile 12 | 13 | COPY . . 14 | 15 | RUN yarn lerna bootstrap 16 | RUN rm -rf /app/packages/client/dist/ && yarn build --scope=client 17 | 18 | 19 | FROM nginx:latest as production 20 | WORKDIR /app 21 | 22 | COPY --from=builder /app/packages/client/dist/ /app/ 23 | COPY --from=builder /app/packages/client/nginx.conf /etc/nginx/nginx.conf 24 | 25 | EXPOSE $CLIENT_PORT 26 | CMD [ "nginx", "-g", "daemon off;" ] 27 | -------------------------------------------------------------------------------- /packages/client/src/components/molecules/Field/Field.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorLine } from "@/components"; 2 | import { Input } from "@/components"; 3 | 4 | interface FieldProps { 5 | label: string; 6 | type: string; 7 | name: string; 8 | error: string | null; 9 | } 10 | 11 | export default function Field(props: FieldProps) { 12 | const { label, type, name, error } = props; 13 | 14 | return ( 15 |
16 | 19 | 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile.server: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=16 2 | ARG SERVER_PORT=3001 3 | 4 | FROM node:$NODE_VERSION-buster as base 5 | 6 | WORKDIR /app 7 | 8 | FROM base as builder 9 | 10 | COPY package.json yarn.lock ./ 11 | RUN yarn install --frozen-lockfile 12 | 13 | COPY . . 14 | 15 | RUN yarn lerna bootstrap 16 | RUN rm -rf /app/packages/server/dist/ && yarn build --scope=server 17 | 18 | 19 | FROM node:$NODE_VERSION-buster-slim as production 20 | WORKDIR /app 21 | 22 | COPY --from=builder /app/packages/server/dist/ /app/ 23 | COPY --from=builder /app/packages/server/package.json /app/package.json 24 | RUN yarn install --production=true 25 | 26 | EXPOSE $SERVER_PORT 27 | CMD [ "node", "/app/index.js" ] -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "paths": { 19 | "@/*": ["./src/*"] 20 | } 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-server-template-with-vite", 3 | "private": true, 4 | "scripts": { 5 | "bootstrap": "yarn && node init.js && lerna clean && yarn && lerna bootstrap", 6 | "build": "lerna run build", 7 | "dev:client": "lerna run dev --scope=client", 8 | "dev:server": "lerna run dev --scope=server", 9 | "dev": "lerna run dev", 10 | "test": "lerna run test", 11 | "lint": "lerna run lint", 12 | "format": "lerna run format", 13 | "preview": "lerna run preview" 14 | }, 15 | "license": "MIT", 16 | "workspaces": [ 17 | "packages/*" 18 | ], 19 | "engines": { 20 | "node": ">=15" 21 | }, 22 | "devDependencies": { 23 | "@evilmartians/lefthook": "^1.3.9", 24 | "lerna": "^5.4.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/server/db.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "pg"; 2 | 3 | const { POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_PORT } = 4 | process.env; 5 | 6 | export const createClientAndConnect = async (): Promise => { 7 | try { 8 | const client = new Client({ 9 | user: POSTGRES_USER, 10 | host: "localhost", 11 | database: POSTGRES_DB, 12 | password: String(POSTGRES_PASSWORD), 13 | port: Number(POSTGRES_PORT), 14 | }); 15 | 16 | await client.connect(); 17 | 18 | const res = await client.query("SELECT NOW()"); 19 | console.log(" ➜ 🎸 Connected to the database at:", res?.rows?.[0].now); 20 | client.end(); 21 | 22 | return client; 23 | } catch (e) { 24 | console.error(e); 25 | } 26 | 27 | return null; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "target": "es2019", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "removeComments": true, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "noImplicitReturns": true, 15 | "noImplicitOverride": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noUnusedParameters": true, 18 | "noUnusedLocals": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "importsNotUsedAsValues": "error", 21 | "lib": ["es2019", "esnext.asynciterable"], 22 | "types": ["node", "jest"], 23 | "baseUrl": ".", 24 | "paths": { 25 | "*": ["types/*"] 26 | }, 27 | "outDir": "dist" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/client/src/services/helpers/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ErrorInfo, ReactNode } from "react"; 2 | 3 | interface Props { 4 | fallback?: ReactNode; 5 | children?: ReactNode; 6 | } 7 | 8 | interface State { 9 | hasError: boolean; 10 | } 11 | 12 | export class ErrorBoundary extends Component { 13 | public state: State = { 14 | hasError: false, 15 | }; 16 | 17 | static getDerivedStateFromError() { 18 | return { hasError: true }; 19 | } 20 | 21 | public componentDidCatch(error: Error, info: ErrorInfo) { 22 | console.error(error, info.componentStack); 23 | } 24 | 25 | public render(): ReactNode { 26 | if (this.state.hasError) { 27 | return ( 28 | this.props.fallback || ( 29 |
Что-то пошло не так
30 | ) 31 | ); 32 | } 33 | 34 | return this.props.children; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/client/src/router/index.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from "react-router-dom"; 2 | import { 3 | Forum, 4 | ForumTopic, 5 | Game, 6 | LeaderBoard, 7 | Profile, 8 | LoginPage, 9 | SignUp, 10 | Error, 11 | } from "../components"; 12 | import Main from "../components/pages/Main"; 13 | 14 | export const router = createBrowserRouter([ 15 | { 16 | path: "/", 17 | element:
, 18 | errorElement: , 19 | }, 20 | { 21 | path: "signin", 22 | element: , 23 | }, 24 | { 25 | path: "signup", 26 | element: , 27 | }, 28 | { 29 | path: "profile", 30 | element: , 31 | }, 32 | { 33 | path: "game", 34 | element: , 35 | }, 36 | { 37 | path: "leaderboard", 38 | element: , 39 | }, 40 | { 41 | path: "forum", 42 | element: , 43 | }, 44 | { 45 | path: "forum/:id", 46 | element: , 47 | }, 48 | ]); 49 | -------------------------------------------------------------------------------- /docs/workFlow.md: -------------------------------------------------------------------------------- 1 | # Порядок работы с проектом 2 | 3 | - [1. GitHub](#1-github) 4 | - [2. Документация](#2-документация) 5 | 6 | ## 1. GitHub 7 | 8 | - Создаем новую ветку от dev ветки, делаем туда коммиты 9 | - Когда все готово создаем PR в основной репо 10 | - Проходим ревью 11 | - Вливаем в ветку dev (через squash & merge) 12 | - Оповещаем всех остальных (чтобы они подтянули изменения из dev и сразу порешали merge conflict) 13 | - В main вливаем только стабильную версию для деплоя 14 | 15 | Шаблон названия веток: CYB-{номер задачи в linear}
16 | Например: 17 | ```markdown 18 | CYB-23 19 | ``` 20 | 21 | Коммиты именуем с кратким описанием действия коммита вначале
22 | Например: 23 | ```markdown 24 | add: logics game, fix: logout error, refactor: update score, remove: unused variable 25 | ``` 26 | 27 | ## 2. Документация 28 | 29 | 1. Создаем файл для описания чего-либо в папке `docs` 30 | 2. Добавляем ссылку на созданный файл в `docs/README.md` 31 | 32 | ```markdown 33 | - [Название раздела](ИмяФайла.md) - Краткое описание 34 | ``` 35 | 36 | 3. Пишем документацию. (Если необходимо добавить изображения создаем папку одноименную файлу) -------------------------------------------------------------------------------- /packages/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "tsc --p ./tsconfig.prod.json", 7 | "preview": "node ./dist/index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon index.ts", 9 | "lint": "eslint .", 10 | "format": "prettier --write .", 11 | "test": "jest ." 12 | }, 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "cross-env": "^7.0.3", 16 | "dotenv": "^16.0.2", 17 | "eslint-config-prettier": "^8.5.0", 18 | "express": "^4.18.1", 19 | "pg": "^8.8.0", 20 | "prettier": "^2.7.1" 21 | }, 22 | "devDependencies": { 23 | "@types/cors": "^2.8.12", 24 | "@types/express": "^4.17.13", 25 | "@types/jest": "^28.1.8", 26 | "@types/pg": "^8.6.5", 27 | "@typescript-eslint/eslint-plugin": "^5.35.1", 28 | "@typescript-eslint/parser": "^5.35.1", 29 | "babel-jest": "^29.0.1", 30 | "eslint": "^8.23.0", 31 | "jest": "^28", 32 | "nodemon": "^2.0.19", 33 | "prettier": "^2.7.1", 34 | "ts-jest": "^28.0.8", 35 | "ts-node": "^10.9.1", 36 | "typescript": "^4.8.2" 37 | }, 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /packages/client/src/components/organisms/LoginForm/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import { Field } from "@/components"; 2 | import { Button } from "@/components"; 3 | 4 | interface FormProps { 5 | title?: string; 6 | isPending?: boolean; 7 | handleSubmit?: () => void; 8 | } 9 | 10 | export default function LoginForm(props: FormProps) { 11 | // mock 12 | const errors = { 13 | email: "wrong signature", 14 | password: "wrong signature", 15 | }; 16 | 17 | return ( 18 |
19 |

20 | BREACH IN 21 |

22 |
23 | 24 | 30 |
31 |
32 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | client: 5 | container_name: prakticum-client 6 | image: prakticum-client 7 | build: 8 | context: . 9 | dockerfile: Dockerfile.client 10 | args: 11 | CLIENT_PORT: ${CLIENT_PORT} 12 | restart: always 13 | ports: 14 | - "${CLIENT_PORT}:80" 15 | environment: 16 | - CLIENT_PORT=${CLIENT_PORT} 17 | - SERVER_PORT=${SERVER_PORT} 18 | server: 19 | container_name: prakticum-server 20 | image: prackicum-server 21 | build: 22 | context: . 23 | dockerfile: Dockerfile.server 24 | args: 25 | SERVER_PORT: ${SERVER_PORT} 26 | restart: always 27 | ports: 28 | - "${SERVER_PORT}:${SERVER_PORT}" 29 | environment: 30 | SERVER_PORT: ${SERVER_PORT} 31 | 32 | postgres: 33 | image: postgres:14 34 | ports: 35 | - "${POSTGRES_PORT}:${POSTGRES_PORT}" 36 | environment: 37 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} 38 | POSTGRES_USER: ${POSTGRES_USER} 39 | POSTGRES_DB: ${POSTGRES_DB} 40 | volumes: 41 | - ./tmp/pgdata:/var/lib/postgresql/data 42 | 43 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "lint": "eslint .", 10 | "format": "prettier --write ." 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^1.9.7", 14 | "dotenv": "^16.0.2", 15 | "eslint-config-prettier": "^8.5.0", 16 | "prettier": "^2.7.1", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-redux": "^8.1.3", 20 | "react-router-dom": "^6.18.0" 21 | }, 22 | "devDependencies": { 23 | "@testing-library/react": "^13.3.0", 24 | "@types/jest": "^28.1.8", 25 | "@types/react": "^18.0.17", 26 | "@types/react-dom": "^18.0.6", 27 | "@typescript-eslint/eslint-plugin": "^5.35.1", 28 | "@typescript-eslint/parser": "^5.35.1", 29 | "@vitejs/plugin-react": "^2.0.1", 30 | "autoprefixer": "^10.4.16", 31 | "eslint": "^8.23.0", 32 | "jest": "^28", 33 | "jest-environment-jsdom": "^29.0.1", 34 | "lefthook": "^1.3.9", 35 | "postcss": "^8.4.31", 36 | "prettier": "^2.7.1", 37 | "tailwindcss": "^3.3.5", 38 | "ts-jest": "^28.0.8", 39 | "typescript": "^4.8.2", 40 | "vite": "^3.0.7" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /docs/scenario.md: -------------------------------------------------------------------------------- 1 | # Сценарий игры 2 | 3 | Игрок должен за отведенное время и количество шагов выбрать нужные комбинации цифр и символов в матрице 5х5 в порядке, указанном в последовательности. Выбор начинается с верхней строчки, второй кусок кода выбирается из столбца, в котором находился первый кусок выбранного кода, третий – из строчки, в которой находился второй выбранного кода и так далее. Выбранные куски кода исчезают из матрицы. Обратный отсчет начнется с момента запуска раунда. Количество последовательностей, необходимых для победы, время и ограничение ходов меняется со сложностью игры. 4 | 5 | Легая сложность: 6 | - 1 последователность из 4 кусков кода; 7 | - 4 шага; 8 | - 30 секунд раунда. 9 | 10 | Средняя сложность: 11 | - 2 последователности из 3х кусков кода; 12 | - 7 шагов; 13 | - 40 секунд раунда. 14 | 15 | Тяжелая сложность: 16 | - 3 последователности из 2х/3х/3х кусков кода; 17 | - 9 шагов; 18 | - 1 минута раунда. 19 | 20 | Пример матрицы 21 | 22 | ![Matrix](scenario/matrix.png) 23 | 24 | Пример последовательности 25 | 26 | ![Subsequence](scenario/subsequence.png) 27 | 28 | _TODO:_ 29 | 30 | - [ ] Реализовать систему поощрений, в виде ачивок. 31 | 32 | _Хотелки:_ 33 | 34 | - [ ] Реализовать анимации для переходов по матрице, удаления кусков кода, успешного или нет завершения последовательности. 35 | - [ ] Добавить возможность изменения размера матрицы для получения большего количества очков. 36 | -------------------------------------------------------------------------------- /packages/client/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Как запускать? 2 | 3 | 1. Убедитесь что у вас установлен `node` и `docker` 4 | 2. Выполните команду `yarn bootstrap` - это обязательный шаг, без него ничего работать не будет :) 5 | 3. Выполните команду `yarn dev` 6 | 3. Выполните команду `yarn dev --scope=client` чтобы запустить только клиент 7 | 4. Выполните команду `yarn dev --scope=server` чтобы запустить только server 8 | 9 | 10 | ### Как добавить зависимости? 11 | В этом проекте используется `monorepo` на основе [`lerna`](https://github.com/lerna/lerna) 12 | 13 | Чтобы добавить зависимость для клиента 14 | ```yarn lerna add {your_dep} --scope client``` 15 | 16 | Для сервера 17 | ```yarn lerna add {your_dep} --scope server``` 18 | 19 | И для клиента и для сервера 20 | ```yarn lerna add {your_dep}``` 21 | 22 | 23 | Если вы хотите добавить dev зависимость, проделайте то же самое, но с флагом `dev` 24 | ```yarn lerna add {your_dep} --dev --scope server``` 25 | 26 | 27 | ### Тесты 28 | 29 | Для клиента используется [`react-testing-library`](https://testing-library.com/docs/react-testing-library/intro/) 30 | 31 | ```yarn test``` 32 | 33 | ### Линтинг 34 | 35 | ```yarn lint``` 36 | 37 | ### Форматирование prettier 38 | 39 | ```yarn format``` 40 | 41 | ### Production build 42 | 43 | ```yarn build``` 44 | 45 | И чтобы посмотреть что получилось 46 | 47 | 48 | `yarn preview --scope client` 49 | `yarn preview --scope server` 50 | 51 | ## Хуки 52 | В проекте используется [lefthook](https://github.com/evilmartians/lefthook) 53 | Если очень-очень нужно пропустить проверки, используйте `--no-verify` (но не злоупотребляйте :) 54 | 55 | ## Ой, ничего не работает :( 56 | 57 | Откройте issue, я приду :) 58 | 59 | ## Автодеплой статики на vercel 60 | Зарегистрируйте аккаунт на [vercel](https://vercel.com/) 61 | Следуйте [инструкции](https://vitejs.dev/guide/static-deploy.html#vercel-for-git) 62 | В качестве `root directory` укажите `packages/client` 63 | 64 | Все ваши PR будут автоматически деплоиться на vercel. URL вам предоставит деплоящий бот 65 | 66 | ## Production окружение в докере 67 | Перед первым запуском выполните `node init.js` 68 | 69 | 70 | `docker compose up` - запустит три сервиса 71 | 1. nginx, раздающий клиентскую статику (client) 72 | 2. node, ваш сервер (server) 73 | 3. postgres, вашу базу данных (postgres) 74 | 75 | Если вам понадобится только один сервис, просто уточните какой в команде 76 | `docker compose up {sevice_name}`, например `docker compose up server` 77 | 78 | [Документация](docs/README.md) --------------------------------------------------------------------------------