├── .husky ├── .gitignore ├── pre-commit └── pre-push ├── .npmrc ├── src ├── lib │ ├── util │ │ ├── version.ts │ │ └── font-color-calculator.ts │ ├── stores │ │ └── selected-labels.store.ts │ ├── contents │ │ └── docs-menu.ts │ └── components │ │ ├── card.svelte │ │ ├── package-version.svelte │ │ ├── docs │ │ ├── menu.svelte │ │ ├── layout.svelte │ │ ├── menu-item.svelte │ │ └── mobile-menu.svelte │ │ ├── repo-header.svelte │ │ ├── loader.svelte │ │ ├── load-more.svelte │ │ ├── label.svelte │ │ ├── filter.svelte │ │ ├── toggle.svelte │ │ ├── search.svelte │ │ ├── footer.svelte │ │ ├── header.svelte │ │ ├── checkbox.svelte │ │ ├── theme-switcher.svelte │ │ ├── profile-picture.svelte │ │ └── issue-card.svelte ├── routes │ ├── +layout.server.ts │ ├── api │ │ ├── version │ │ │ └── +server.ts │ │ ├── authentication │ │ │ ├── login │ │ │ │ └── +server.ts │ │ │ ├── logout │ │ │ │ └── +server.ts │ │ │ └── callback │ │ │ │ └── +server.ts │ │ └── get-issues │ │ │ └── +server.ts │ ├── +layout.ts │ ├── docs │ │ ├── +layout.svelte │ │ ├── +page.md │ │ └── testing │ │ │ └── +page.md │ ├── +layout.svelte │ ├── login │ │ └── +page.svelte │ ├── +page.ts │ └── +page.svelte ├── app.d.ts ├── hooks.server.ts ├── app.html ├── app.css └── global.d.ts ├── static ├── favicon.ico ├── images │ ├── hubber.png │ ├── collapse.svg │ ├── collapse-dark.svg │ ├── githubmark-light.svg │ ├── githubmark-dark.svg │ └── open-in-gitpod.svg ├── js │ └── theme.js └── prism.css ├── .prettierrc ├── .env.example ├── .gitignore ├── SECURITY.md ├── .prettierignore ├── kubernetes ├── service.yaml ├── ingress.yaml └── deployment.yaml ├── tsconfig.json ├── postcss.config.cjs ├── .github └── workflows │ ├── build.yml │ ├── labels.yml │ ├── kubescape.yml │ ├── release.yml │ ├── test.yml │ └── deploy.yml ├── Dockerfile ├── mdsvex.config.js ├── .gitpod.yml ├── vite.config.js ├── .eslintrc.cjs ├── playwright.config.ts ├── svelte.config.js ├── LICENSE ├── tailwind.config.cjs ├── CHANGELOG.md ├── package.json ├── tests └── login.spec.ts ├── README.md ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/lib/util/version.ts: -------------------------------------------------------------------------------- 1 | export const versionKey = {}; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tccpro/good-first-issue-finder/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx svelte-check --fail-on-hints 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | FINDER_GITHUB_CLIENT_ID= 2 | FINDER_GITHUB_CLIENT_SECRET= 3 | -------------------------------------------------------------------------------- /static/images/hubber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tccpro/good-first-issue-finder/HEAD/static/images/hubber.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /src/lib/stores/selected-labels.store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const selectedLabels = writable([]); 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | - Please do not create GitHub issues to report security vulnerabilities. 2 | - Instead, report them via http://eddiejaoude.io/contact. 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignoring CHANGELOG.md because it will get overwritten by the next release. We can commit it, but the changes won't be durable. 2 | CHANGELOG.md 3 | -------------------------------------------------------------------------------- /src/lib/contents/docs-menu.ts: -------------------------------------------------------------------------------- 1 | export const menu = [ 2 | { 3 | name: 'Introduction', 4 | href: '/docs', 5 | }, 6 | { 7 | name: 'Testing', 8 | href: '/docs/testing', 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/lib/components/card.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /kubernetes/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: eddiehub-finder-service 5 | spec: 6 | selector: 7 | app: eddiehub-finder 8 | ports: 9 | - protocol: TCP 10 | port: 3000 11 | targetPort: 3000 12 | -------------------------------------------------------------------------------- /src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from '../../.svelte-kit/types/src/routes/$types'; 2 | 3 | export const load: LayoutServerLoad = ({ locals }): { username: string } => { 4 | return { 5 | username: locals.user, 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/routes/api/version/+server.ts: -------------------------------------------------------------------------------- 1 | import { type RequestHandler, json } from '@sveltejs/kit'; 2 | import { env } from '$env/dynamic/private'; 3 | 4 | export const GET: RequestHandler = async () => { 5 | return json({ version: env.npm_package_version }); 6 | }; 7 | -------------------------------------------------------------------------------- /static/images/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/components/package-version.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | v{version} 10 |
11 | -------------------------------------------------------------------------------- /static/images/collapse-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/components/docs/menu.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
    7 | {#each menu as item} 8 |
  • 9 | 10 |
  • 11 | {/each} 12 |
13 | -------------------------------------------------------------------------------- /src/lib/components/docs/layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

{title}

10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | // and what to do when importing types 4 | declare namespace App { 5 | interface Locals { 6 | user: string; 7 | } 8 | 9 | // interface Platform {} 10 | 11 | // interface PrivateEnv {} 12 | 13 | // interface PublicEnv {} 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import type { Load } from '@sveltejs/kit'; 2 | 3 | export const load: Load = async ({ fetch, data }) => { 4 | const res = await fetch('/api/version'); 5 | if (res.ok) { 6 | const resData = (await res.json()) as { version: string }; 7 | return { 8 | ...data, 9 | version: resData.version, 10 | }; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import type { Handle } from '@sveltejs/kit'; 2 | import cookie from 'cookie'; 3 | 4 | export const handle: Handle = async ({ event, resolve }) => { 5 | const cookies = cookie.parse(event.request.headers.get('cookie') || '') || ''; 6 | event.locals.user = cookies.user; 7 | 8 | const response = await resolve(event); 9 | 10 | return response; 11 | }; 12 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer, 10 | ], 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /src/lib/components/docs/menu-item.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {menuItem.name} 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/lib/components/repo-header.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | {owner}/{repo} 15 |
16 | -------------------------------------------------------------------------------- /src/lib/components/loader.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | branches: [main] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | packages: read 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '16' 17 | - name: install dependencies 18 | run: npm ci 19 | - name: run build 20 | run: npm run build 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine AS dependencies 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm ci --ignore-scripts 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | FROM node:16-alpine AS production 14 | 15 | WORKDIR /usr/src/app 16 | 17 | COPY package*.json ./ 18 | 19 | RUN npm ci --only=production --ignore-scripts 20 | 21 | COPY . . 22 | 23 | COPY --from=dependencies /usr/src/app/build ./build 24 | 25 | EXPOSE 3000 26 | 27 | CMD ["npm", "run", "run:server"] 28 | -------------------------------------------------------------------------------- /mdsvex.config.js: -------------------------------------------------------------------------------- 1 | import { defineMDSveXConfig as defineConfig } from 'mdsvex'; 2 | import autolinkHeadings from 'rehype-autolink-headings'; 3 | import slugPlugin from 'rehype-slug'; 4 | 5 | const config = defineConfig({ 6 | extensions: ['.md'], 7 | layout: { 8 | docs: './src/lib/components/docs/layout.svelte', 9 | }, 10 | 11 | smartypants: { 12 | dashes: 'oldschool', 13 | }, 14 | 15 | remarkPlugins: [], 16 | rehypePlugins: [slugPlugin, [autolinkHeadings, { behavior: 'append' }]], 17 | }); 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: Import open source standard labels 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | labels: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: '16' 14 | - uses: EddieHubCommunity/gh-action-open-source-labels@v0.2.2 15 | with: 16 | github-token: ${{ secrets.GITHUB_TOKEN }} 17 | owner-name: ${{ github.repository_owner }} 18 | repository-name: ${{ github.event.repository.name }} 19 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-full-vnc:latest 2 | 3 | tasks: 4 | - init: npm install # runs during prebuild 5 | command: | 6 | export HMR_HOST=`gp url 5173` 7 | 8 | ports: 9 | - port: 5173 10 | onOpen: ignore 11 | 12 | github: 13 | prebuilds: 14 | master: true 15 | branches: true 16 | pullRequests: true 17 | pullRequestsFromForks: true 18 | addCheck: true 19 | addComment: false 20 | addBadge: true 21 | 22 | vscode: 23 | extensions: 24 | - humao.rest-client 25 | - svelte.svelte-vscode 26 | - bradlc.vscode-tailwindcss 27 | -------------------------------------------------------------------------------- /src/routes/docs/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 10 |
11 | 12 |
13 |
14 | 15 |
16 | 18 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import pkg from './package.json'; 3 | 4 | /** @type {import('vite').UserConfig} */ 5 | const config = ({ command }) => ({ 6 | ssr: command === 'build' && { 7 | external: Object.keys(pkg.dependencies), 8 | noExternal: true, 9 | }, 10 | plugins: [sveltekit()], 11 | server: { 12 | hmr: { 13 | clientPort: process.env.HMR_HOST ? 443 : 5173, 14 | host: process.env.HMR_HOST ? process.env.HMR_HOST.substring('https://'.length) : 'localhost', 15 | }, 16 | }, 17 | }); 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /src/lib/components/load-more.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs', '*.css'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript'), 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020, 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/kubescape.yml: -------------------------------------------------------------------------------- 1 | name: Kubescape 2 | on: 3 | push: 4 | paths: 5 | - 'kubernetes/**' 6 | pull_request: 7 | paths: 8 | - 'kubernetes/**' 9 | 10 | jobs: 11 | security: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: '16' 18 | - name: Install Kubescape 19 | run: curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash 20 | - name: Kubescape 21 | run: kubescape scan kubernetes/*.yaml -v --fail-threshold 40 22 | -------------------------------------------------------------------------------- /src/lib/components/label.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
18 | {@html text} 19 |
20 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | Good First Issue Finder 13 | %sveltekit.head% 14 | 15 | 16 |
%sveltekit.body%
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/filter.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
    16 | {#each tags as tag} 17 |
  • 18 | 19 |
  • 20 | {/each} 21 |
22 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { type PlaywrightTestConfig, devices } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | projects: [ 5 | { 6 | name: 'chromium', 7 | use: { ...devices['Desktop Chrome'] }, 8 | }, 9 | { 10 | name: 'firefox', 11 | use: { ...devices['Desktop Firefox'] }, 12 | }, 13 | { 14 | name: 'webkit', 15 | use: { ...devices['Desktop Safari'] }, 16 | }, 17 | ], 18 | webServer: { 19 | command: 'npm run build && npm run preview', 20 | port: 4173, 21 | }, 22 | use: { 23 | baseURL: 'http://localhost:4173', 24 | }, 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/routes/login/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |

Login

8 |

Please Login with your Github Account

9 | 10 | Github Icon 11 |
Login with Github
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /src/lib/util/font-color-calculator.ts: -------------------------------------------------------------------------------- 1 | export function calculateFontColor(hexColor: string, threshold?: number): string { 2 | const pThreshold = threshold || 0.5; 3 | const sanitized = hexColor.replace('#', ''); 4 | const red = parseInt(sanitized.substring(0, 2), 16); 5 | const green = parseInt(sanitized.substring(2, 4), 16); 6 | const blue = parseInt(sanitized.substring(4, 6), 16); 7 | 8 | const pRed = 0.299; 9 | const pGreen = 0.587; 10 | const pBlue = 0.114; 11 | 12 | const contrast = Math.sqrt( 13 | pRed * (red / 255) ** 2 + pGreen * (green / 255) ** 2 + pBlue * (blue / 255) ** 2, 14 | ); 15 | 16 | return contrast > pThreshold ? '#000000' : '#ffffff'; 17 | } 18 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { mdsvex } from 'mdsvex'; 2 | import mdsvexConfig from './mdsvex.config.js'; 3 | import adapter from '@sveltejs/adapter-node'; 4 | import preprocess from 'svelte-preprocess'; 5 | 6 | /** @type {import('@sveltejs/kit').Config} */ 7 | const config = { 8 | extensions: ['.svelte', ...mdsvexConfig.extensions], 9 | 10 | // Consult https://github.com/sveltejs/svelte-preprocess 11 | // for more information about preprocessors 12 | preprocess: [ 13 | preprocess({ 14 | postcss: true, 15 | }), 16 | mdsvex(mdsvexConfig), 17 | ], 18 | 19 | kit: { 20 | adapter: adapter(), 21 | files: { 22 | lib: 'src/lib', 23 | }, 24 | }, 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | @layer base { 7 | :root { 8 | --color-primary: #ff5a00; 9 | --color-background: #f5f4f4; 10 | --color-off-background: #ffffff; 11 | --color-text: #22272e; 12 | } 13 | 14 | .dark { 15 | --color-primary: #ff5a00; 16 | --color-background: #22272e; 17 | --color-off-background: #333a43; 18 | --color-text: #dadada99; 19 | --color-text-highlight: #dadadade; 20 | } 21 | body { 22 | @apply bg-skin-background text-skin-text; 23 | } 24 | } 25 | 26 | @layer utilities { 27 | .default-transition { 28 | @apply transition-all delay-[50ms] duration-200; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/components/docs/mobile-menu.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#if isShown} 13 |
    14 | {#each menu as item} 15 |
  • 16 | {item.name} 19 |
  • 20 | {/each} 21 |
22 | {:else} 23 | 24 | {/if} 25 |
26 | -------------------------------------------------------------------------------- /src/routes/api/authentication/login/+server.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from '@sveltejs/kit'; 2 | import cookie from 'cookie'; 3 | import { env } from '$env/dynamic/private'; 4 | 5 | const ghAuthURL = 'https://github.com/login/oauth/authorize'; 6 | const clientId = env.FINDER_GITHUB_CLIENT_ID; 7 | export const GET: RequestHandler = async () => { 8 | const csrfState = Math.random().toString(36).substring(7); 9 | const csrfCookie = cookie.serialize('state', csrfState, { 10 | maxAge: 1000 * 60 * 3, 11 | httpOnly: true, 12 | }); 13 | 14 | return new Response(null, { 15 | status: 302, 16 | headers: { 17 | 'set-cookie': csrfCookie, 18 | location: `${ghAuthURL}?client_id=${clientId}&state=${csrfState}`, 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /kubernetes/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: eddiehub-finder-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/rewrite-target: / 7 | spec: 8 | ingressClassName: nginx 9 | rules: 10 | - host: 'finder.eddiehub.org' 11 | http: 12 | paths: 13 | - path: / 14 | pathType: Prefix 15 | backend: 16 | service: 17 | name: eddiehub-finder-service 18 | port: 19 | number: 3000 20 | - host: 'finder.eddiehub.io' 21 | http: 22 | paths: 23 | - path: / 24 | pathType: Prefix 25 | backend: 26 | service: 27 | name: eddiehub-finder-service 28 | port: 29 | number: 3000 30 | -------------------------------------------------------------------------------- /src/routes/api/authentication/logout/+server.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from '@sveltejs/kit'; 2 | import cookie from 'cookie'; 3 | 4 | export const GET: RequestHandler = async ({ locals }) => { 5 | locals.user = ''; 6 | const expiry = new Date('Thu, 01 Jan 1970 00:00:00 GMT'); 7 | const invalidAccessToken = cookie.serialize('access_token', '', { 8 | expires: expiry, 9 | httpOnly: true, 10 | path: '/', 11 | }); 12 | const invalidUsername = cookie.serialize('user', '', { 13 | expires: expiry, 14 | httpOnly: true, 15 | path: '/', 16 | }); 17 | 18 | const headers = new Headers(); 19 | headers.append('set-cookie', invalidAccessToken); 20 | headers.append('set-cookie', invalidUsername); 21 | headers.append('location', '/'); 22 | 23 | return new Response(null, { 24 | status: 302, 25 | headers, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | changelog: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Conventional Changelog Action 15 | id: changelog 16 | uses: TriPSs/conventional-changelog-action@v3 17 | with: 18 | github-token: ${{ secrets.CHANGELOG_RELEASE }} 19 | 20 | - name: Create Release 21 | uses: actions/create-release@v1 22 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.CHANGELOG_RELEASE }} 25 | with: 26 | tag_name: ${{ steps.changelog.outputs.tag }} 27 | release_name: ${{ steps.changelog.outputs.tag }} 28 | body: ${{ steps.changelog.outputs.clean_changelog }} 29 | -------------------------------------------------------------------------------- /src/lib/components/toggle.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
11 | 16 |
17 | 22 |
23 | 24 | 29 | -------------------------------------------------------------------------------- /static/images/githubmark-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /kubernetes/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: eddiehub-finder-deployment 5 | labels: 6 | app: eddiehub-finder 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: eddiehub-finder 12 | template: 13 | metadata: 14 | labels: 15 | app: eddiehub-finder 16 | spec: 17 | containers: 18 | - name: finder 19 | image: ghcr.io/eddiehubcommunity/finder:latest 20 | ports: 21 | - containerPort: 3000 22 | env: 23 | - name: FINDER_GITHUB_CLIENT_ID 24 | valueFrom: 25 | secretKeyRef: 26 | name: finder-gh-client-id-secret 27 | key: FINDER_GITHUB_CLIENT_ID 28 | - name: FINDER_GITHUB_CLIENT_SECRET 29 | valueFrom: 30 | secretKeyRef: 31 | name: finder-gh-client-secret 32 | key: FINDER_GITHUB_CLIENT_SECRET 33 | -------------------------------------------------------------------------------- /static/images/githubmark-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /static/js/theme.js: -------------------------------------------------------------------------------- 1 | const theme = localStorage.getItem('theme'); 2 | const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 3 | 4 | if (theme === null) { 5 | localStorage.setItem('theme', 'system'); 6 | if (darkMediaQuery.matches) { 7 | document.body.classList.add('dark'); 8 | } else { 9 | document.body.classList.add('light'); 10 | } 11 | } else if (theme === 'dark') { 12 | document.body.classList.add('dark'); 13 | } else if (theme === 'light') { 14 | document.body.classList.add('light'); 15 | } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 16 | document.body.classList.add('dark'); 17 | } else { 18 | document.body.classList.add('light'); 19 | } 20 | 21 | darkMediaQuery.addEventListener('change', () => { 22 | if (theme === 'system') { 23 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 24 | document.body.classList.replace('light', 'dark'); 25 | } else { 26 | document.body.classList.replace('dark', 'light'); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/lib/components/search.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /src/routes/docs/+page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | Welcome developers! This docs should be the entry point to the documentation. In case things remain unclear about it, feel free to raise an issue in the [Finder-Project](https://github.com/EddieHubCommunity/good-first-issue-finder). 6 | 7 | ## Key Features 8 | 9 | Very technically spoken, this project uses the GitHub-API for crawling issues, that are labeled with a specific label: 10 | 11 | - You can either crawl the [EddieHubCommunity](https://github.com/EddieHubCommunity) for issues that are labeled with `good-first-issue` 12 | - or entire Github for the label `EddieHub:good-first-issue` 13 | 14 | ## Technologies 15 | 16 | - [Sveltekit](https://kit.svelte.dev/) - as app-framework for the frontend 17 | - [Tailwind](https://tailwindcss.com/) - as css-framework to get consistent stylings 18 | - [Playwright](https://playwright.dev/) - for running end-to-end tests 19 | - [GitHub](https://www.github.com) - as identity provider for the login, and API 20 | - [GitHub GraphQL API](https://docs.github.com/en/graphql) - for crawling the GitHub API 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 EddieHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib/components/footer.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 | 9 | 10 |
    11 |
  • 12 | 19 |
  • 20 |
  • 21 | 28 |
  • 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | import { error, redirect, type Load } from '@sveltejs/kit'; 2 | import type { SearchResponse } from '../global'; 3 | 4 | export const load: Load = async ({ fetch, url, parent }) => { 5 | const parentData = await parent(); 6 | if (!parentData.username) { 7 | throw redirect(307, '/login'); 8 | } 9 | let globalParam = false; 10 | const globalQuery = 'is:open label:"EddieHub:good-first-issue" no:assignee'; 11 | const orgQuery = 'is:open label:"good first issue" org:EddieHubCommunity no:assignee'; 12 | 13 | try { 14 | globalParam = JSON.parse(url.searchParams.get('global') as string); 15 | } catch (err) { 16 | globalParam = false; 17 | } 18 | 19 | const postBody: { query: string } = globalParam ? { query: globalQuery } : { query: orgQuery }; 20 | 21 | const res = await fetch('/api/get-issues', { 22 | method: 'POST', 23 | body: JSON.stringify(postBody), 24 | }); 25 | if (res.ok) { 26 | const data = await res.json(); 27 | return { 28 | data: data as SearchResponse, 29 | checked: globalParam, 30 | }; 31 | } 32 | if (res.status === 401) { 33 | throw redirect(307, '/login'); 34 | } 35 | const data = await res.json(); 36 | throw error(500, data.message); 37 | }; 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | Tests: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Install Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | # - name: Get installed Playwright version 25 | # id: playwright-version 26 | # run: echo -n "::set-output name=version::$(npm ls @playwright/test --json | jq --raw-output '.dependencies["@playwright/test"].version')" 27 | 28 | # - uses: actions/cache@v3 29 | # id: playwright-cache 30 | # with: 31 | # path: '~/.cache/ms-playwright' 32 | # key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' 33 | # restore-keys: | 34 | # ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }} 35 | 36 | - name: Install Playwright's dependencies 37 | # if: steps.playwright-cache.outputs.cache-hit != 'true' 38 | run: npx playwright install --with-deps 39 | 40 | - name: Run tests 41 | run: npm run test 42 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export declare type Owner = { 4 | avatarUrl: string; 5 | login: string; 6 | }; 7 | 8 | export declare type Repository = { 9 | name: string; 10 | url: string; 11 | owner: Owner; 12 | primaryLanguage: PrimaryLanguage; 13 | codeOfConduct: CodeOfConduct; 14 | licenseInfo: LicenseInfo; 15 | }; 16 | 17 | export declare type CodeOfConduct = { 18 | name: string; 19 | }; 20 | 21 | export declare type LicenseInfo = { 22 | name: string; 23 | }; 24 | 25 | export declare type Node = { 26 | createdAt: string; 27 | url: string; 28 | title: string; 29 | labels: LabelResponse; 30 | repository: Repository; 31 | }; 32 | 33 | export declare type Edge = { 34 | node: Node; 35 | }; 36 | 37 | export declare type pageInfo = { 38 | hasNextPage: boolean; 39 | endCursor: string; 40 | }; 41 | 42 | export declare type SearchResponse = { 43 | labels: string[]; 44 | issueCount: number; 45 | pageInfo: pageInfo; 46 | edges: Edge[]; 47 | }; 48 | 49 | export declare type LabelResponse = { 50 | edges: LabelEdge[]; 51 | }; 52 | 53 | export declare type LabelEdge = { 54 | node: LabelNode; 55 | }; 56 | 57 | export declare type LabelNode = { 58 | color: string; 59 | name: string; 60 | }; 61 | 62 | export declare type PrimaryLanguage = { 63 | color: string; 64 | name: string; 65 | id: string; 66 | }; 67 | -------------------------------------------------------------------------------- /src/lib/components/header.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 | hubber 12 | 13 |
14 |
    15 | {#each navItems as item} 16 |
  • 17 | {item.name} 23 |
  • 24 | {/each} 25 |
26 | {#if username} 27 | 28 | {:else} 29 | Login 35 | {/if} 36 |
37 |
38 | 39 | 44 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | content: ['./src/**/*.{html,js,svelte,ts}'], 3 | darkMode: 'class', 4 | theme: { 5 | extend: { 6 | colors: { 7 | skin: { 8 | primary: 'var(--color-primary)', 9 | background: 'var(--color-background)', 10 | 'off-background': 'var(--color-off-background)', 11 | text: 'var(--color-text)', 12 | 'text-highlight': 'var(--color-text-highlight)', 13 | }, 14 | }, 15 | boxShadow: { 16 | standard: 'rgba(0, 0, 0, 0.05) 0px 1rem 2rem', 17 | dark: 'rgba(10, 12, 14, 0.9) 0px 1rem 2rem', 18 | }, 19 | typography: { 20 | DEFAULT: { 21 | css: { 22 | code: { 23 | color: 'var(--text)', 24 | }, 25 | color: 'var(--text)', 26 | h1: { 27 | color: 'var(--color-text-highlight)', 28 | }, 29 | h2: { 30 | color: 'var(--color-text-highlight)', 31 | }, 32 | h3: { 33 | color: 'var(--color-text-highlight)', 34 | }, 35 | h4: { 36 | color: 'var(--color-text-highlight)', 37 | }, 38 | h5: { 39 | color: 'var(--color-text-highlight)', 40 | }, 41 | h6: { 42 | color: 'var(--color-text-highlight)', 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | }, 49 | 50 | plugins: [require('@tailwindcss/typography')], 51 | }; 52 | 53 | module.exports = config; 54 | -------------------------------------------------------------------------------- /src/lib/components/checkbox.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 | 38 | 39 |
40 | 41 | 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.7.0](https://github.com/EddieHubCommunity/good-first-issue-finder/compare/v2.6.1...v2.7.0) (2022-09-30) 2 | 3 | 4 | ### Features 5 | 6 | * Display Code of Conduct & License ([#229](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/229)) ([c51ab21](https://github.com/EddieHubCommunity/good-first-issue-finder/commit/c51ab218833738b445b8f0a93c0458c0d704a8d9)) 7 | 8 | 9 | 10 | ## [2.6.1](https://github.com/EddieHubCommunity/good-first-issue-finder/compare/v2.6.0...v2.6.1) (2022-09-21) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * typos ([#228](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/228)) ([8f9b972](https://github.com/EddieHubCommunity/good-first-issue-finder/commit/8f9b972cc562079737583721e00072f18642a468)) 16 | 17 | 18 | 19 | # [2.6.0](https://github.com/EddieHubCommunity/good-first-issue-finder/compare/v2.5.0...v2.6.0) (2022-09-21) 20 | 21 | 22 | ### Features 23 | 24 | * add docs layout ([#222](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/222)) ([d8a2143](https://github.com/EddieHubCommunity/good-first-issue-finder/commit/d8a214385141f219c218eaa24b4479485d3c6833)) 25 | 26 | 27 | 28 | # [2.5.0](https://github.com/EddieHubCommunity/good-first-issue-finder/compare/v2.4.0...v2.5.0) (2022-09-19) 29 | 30 | 31 | ### Features 32 | 33 | * update sveltekit & add small documentation ([#219](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/219)) ([e1e903c](https://github.com/EddieHubCommunity/good-first-issue-finder/commit/e1e903cdcbc5d0be306c855e5f800db7e20afa5e)) 34 | 35 | 36 | 37 | # [2.4.0](https://github.com/EddieHubCommunity/good-first-issue-finder/compare/v2.3.1...v2.4.0) (2022-09-15) 38 | 39 | 40 | ### Features 41 | 42 | * add accessible checkboxes ([#214](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/214)) ([9f3b01c](https://github.com/EddieHubCommunity/good-first-issue-finder/commit/9f3b01cd3108ef21e9b7c687bdf8adae1ac8e3aa)) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/routes/docs/testing/+page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing 3 | --- 4 | 5 | This project uses [Playwright](https://playwright.dev/) as testing library for running End-To-End tests. 6 | 7 | ## Adding skeleton tests 8 | 9 | New tests are added in the `tests` directory of the project. Currently the tests are structured by pages, e.g `login.spec.ts`. 10 | 11 | Test files always need to have the ending of `.spec.ts` in order to get picked up the Playwright. You don't always need to create a new file. It's very likely that an existing one can be used. 12 | 13 | To make it easier for others to contribute, Playwright offers the possibility to initialize skeleton-tests. The important part is, that the `test`-function calls `fixme`. 14 | 15 | Once this is in place, please add comments to describe the required steps that the test needs to do, in order to be seen as successful. These comments can be written in plain English. 16 | 17 | The following code snippet shows an example for a skeleton test. 18 | 19 | ```ts 20 | test.fixme('', async () => { 21 | // navigate to example page 22 | // check if the page contains element 23 | }); 24 | ``` 25 | 26 | ## Filling out skeleton tests 27 | 28 | We highly encourage people to only fill out one test per Pull Request, so other people are also having the chance to contribute to this initiative. 29 | 30 | To fill out a test, pick one, that has the prefix `test.fixme`. 31 | 32 | Ideally you can take other, existing tests as a reference for your test to get started. 33 | 34 | Every skeleton contains a couple of comments telling you the steps that are required for the test to be seen as successful. 35 | 36 | Once you're ready with filling out the skeleton, rename `test.fixme` to `test`. 37 | 38 | A filled out skeleton may look like the following example: 39 | 40 | ```ts 41 | test.describe('Login Card', () => { 42 | test('h2 contains desired text', async ({ page }) => { 43 | // navigate to the login page 44 | await page.goto('/login'); 45 | 46 | //check if the h2 element contains the desired text 47 | expect(await page.textContent('h2')).toBe('Login'); 48 | }); 49 | ``` 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "issue-finder", 3 | "version": "2.7.0", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "vite build", 7 | "run:server": "node build/index.js", 8 | "package": "svelte-kit package", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 13 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", 14 | "validate": "npx svelte-check --fail-on-hints", 15 | "prepare": "husky install", 16 | "test": "playwright test" 17 | }, 18 | "devDependencies": { 19 | "@playwright/test": "^1.25.2", 20 | "@popperjs/core": "^2.11.6", 21 | "@sveltejs/adapter-node": "1.0.0-next.92", 22 | "@sveltejs/kit": "1.0.0-next.485", 23 | "@tailwindcss/typography": "^0.5.7", 24 | "@types/cookie": "^0.5.1", 25 | "@typescript-eslint/eslint-plugin": "^4.31.1", 26 | "@typescript-eslint/parser": "^4.31.1", 27 | "autoprefixer": "^10.4.8", 28 | "eslint": "^7.32.0", 29 | "eslint-config-prettier": "^8.5.0", 30 | "eslint-plugin-svelte3": "^3.2.1", 31 | "husky": "^7.0.4", 32 | "lint-staged": "^12.3.2", 33 | "mdsvex": "^0.10.6", 34 | "postcss": "^8.4.16", 35 | "postcss-load-config": "^3.1.1", 36 | "prettier": "^2.7.1", 37 | "prettier-plugin-svelte": "^2.7.0", 38 | "prettier-plugin-tailwindcss": "^0.1.13", 39 | "rehype-autolink-headings": "^6.1.1", 40 | "rehype-slug": "^5.0.1", 41 | "svelte": "^3.50.0", 42 | "svelte-check": "^2.9.0", 43 | "svelte-preprocess": "^4.10.7", 44 | "tailwindcss": "^3.1.8", 45 | "tslib": "^2.4.0", 46 | "typescript": "^4.8.2", 47 | "vite": "^3.1.0" 48 | }, 49 | "type": "module", 50 | "dependencies": { 51 | "cookie": "^0.5.0", 52 | "node-fetch": "^2.6.7", 53 | "octokit": "^2.0.7" 54 | }, 55 | "license": "MIT", 56 | "lint-staged": { 57 | "*.{cjs,js,ts,css,scss,svelte}": [ 58 | "eslint --fix", 59 | "prettier --write --plugin-search-dir=." 60 | ] 61 | } 62 | } -------------------------------------------------------------------------------- /src/routes/api/authentication/callback/+server.ts: -------------------------------------------------------------------------------- 1 | import { env } from '$env/dynamic/private'; 2 | import type { RequestHandler } from '@sveltejs/kit'; 3 | import { error } from '@sveltejs/kit'; 4 | import fetch from 'node-fetch'; 5 | import cookie from 'cookie'; 6 | 7 | const tokenURL = 'https://github.com/login/oauth/access_token'; 8 | const userURL = 'https://api.github.com/user'; 9 | 10 | const clientId = env.FINDER_GITHUB_CLIENT_ID; 11 | const secret = env.FINDER_GITHUB_CLIENT_SECRET; 12 | 13 | export const GET: RequestHandler = async ({ url, request }) => { 14 | const code = url.searchParams.get('code') as string; 15 | const state = url.searchParams.get('state') as string; 16 | 17 | const token = await getAccessToken(code); 18 | const user = await getUser(token); 19 | 20 | const csrfToken = cookie.parse(request.headers.get('cookie') || '').state || ''; 21 | 22 | if (state !== csrfToken) { 23 | throw error(403, 'CSRF Token not matching'); 24 | } 25 | 26 | const tokenCookie = cookie.serialize('access_token', token, { httpOnly: true, path: '/' }); 27 | const userCookie = cookie.serialize('user', user.login || '', { httpOnly: true, path: '/' }); 28 | 29 | const headers = new Headers(); 30 | headers.append('location', '/'); 31 | headers.append('set-cookie', tokenCookie); 32 | headers.append('set-cookie', userCookie); 33 | 34 | return new Response(null, { 35 | status: 302, 36 | headers, 37 | }); 38 | }; 39 | 40 | async function getAccessToken(code: string) { 41 | const r = await fetch(tokenURL, { 42 | method: 'POST', 43 | headers: { 44 | 'Content-Type': 'application/json', 45 | Accept: 'application/json', 46 | }, 47 | body: JSON.stringify({ 48 | client_id: clientId, 49 | client_secret: secret, 50 | code, 51 | }), 52 | }); 53 | const r_1 = (await r.json()) as { 54 | access_token: string; 55 | token_type: string; 56 | scope: string; 57 | }; 58 | return r_1.access_token; 59 | } 60 | 61 | async function getUser(accessToken: string) { 62 | const r = await fetch(userURL, { 63 | headers: { 64 | Accept: 'application/json', 65 | Authorization: `Bearer ${accessToken}`, 66 | }, 67 | }); 68 | return r.json(); 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/components/theme-switcher.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
37 | 46 | 55 | 61 |
62 | 63 | 68 | -------------------------------------------------------------------------------- /src/lib/components/profile-picture.svelte: -------------------------------------------------------------------------------- 1 | 43 | 44 |
45 | 55 | {#if displayPopover} 56 | 80 | -------------------------------------------------------------------------------- /src/routes/api/get-issues/+server.ts: -------------------------------------------------------------------------------- 1 | import { json as json$1 } from '@sveltejs/kit'; 2 | import type { RequestHandler } from '@sveltejs/kit'; 3 | import { Octokit } from 'octokit'; 4 | import cookie from 'cookie'; 5 | import type { SearchResponse } from 'src/global'; 6 | 7 | type Response = { search: SearchResponse }; 8 | 9 | export const POST: RequestHandler = async ({ request }) => { 10 | const token = cookie.parse(request.headers.get('cookie') || '').access_token || ''; 11 | 12 | if (!token) { 13 | return new Response(JSON.stringify({ message: 'Please authenticate to use this endpoint' }), { 14 | status: 401, 15 | }); 16 | } 17 | 18 | const body = (await request.json()) as { query: string; after?: string }; 19 | 20 | const octokit = new Octokit({ auth: token }); 21 | const { search }: Response = await octokit.graphql( 22 | `query EddieHubIssues($queryString: String!, $skip: Int!, $after: String) { 23 | search(first: $skip, query: $queryString, type: ISSUE, after: $after) { 24 | issueCount 25 | pageInfo { 26 | hasNextPage 27 | endCursor 28 | } 29 | edges { 30 | node { 31 | ... on Issue { 32 | url 33 | title 34 | createdAt 35 | labels(first: $skip) { 36 | edges { 37 | node { 38 | color 39 | name 40 | } 41 | } 42 | } 43 | repository { 44 | name 45 | url 46 | primaryLanguage { 47 | color 48 | name 49 | id 50 | } 51 | owner { 52 | avatarUrl 53 | login 54 | } 55 | codeOfConduct { 56 | id 57 | name 58 | url 59 | } 60 | licenseInfo { 61 | name 62 | id 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | }`, 70 | { 71 | queryString: body.query, 72 | skip: 10, 73 | after: body.after, 74 | }, 75 | ); 76 | const labels = search.edges.map((el) => el.node.labels.edges.map((label) => label.node.name)); 77 | const merged = labels.reduce((acc, val) => { 78 | return acc.concat(val); 79 | }); 80 | const uniqueLabels = [...new Set(merged)]; 81 | const returnBody = { ...search, ...{ labels: uniqueLabels } }; 82 | 83 | return json$1(returnBody, { status: 200 }); 84 | }; 85 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build & publish Docker image then deploy 2 | on: 3 | workflow_dispatch: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: '16' 15 | - name: install dependencies 16 | run: npm ci 17 | - name: run build 18 | run: npm run build 19 | - uses: actions/upload-artifact@main 20 | with: 21 | name: artifacts 22 | path: build/ 23 | 24 | push_to_registry: 25 | name: Push Docker image to GitHub Packages 26 | needs: build 27 | permissions: 28 | contents: read 29 | packages: write 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: check out the repo 33 | uses: actions/checkout@v2 34 | - name: get-npm-version 35 | id: package-version 36 | uses: martinbeentjes/npm-get-version-action@master 37 | - name: set up Docker builder 38 | uses: docker/setup-buildx-action@v1 39 | - name: log into GitHub Container Registry 40 | uses: docker/login-action@v1 41 | with: 42 | registry: ghcr.io 43 | username: ${{ github.repository_owner }} 44 | password: ${{ secrets.CR_PAT }} 45 | - name: push to Github Container Registry 46 | uses: docker/build-push-action@v2 47 | with: 48 | context: . 49 | push: true 50 | secrets: | 51 | 'GH_TOKEN=${{ secrets.GITHUB_TOKEN }}' 52 | build-args: 'github_token=${{ secrets.GITHUB_TOKEN }}' 53 | tags: | 54 | ghcr.io/eddiehubcommunity/finder:v${{ steps.package-version.outputs.current-version}} 55 | ghcr.io/eddiehubcommunity/finder:latest 56 | deploy: 57 | name: deploy to kube cluster 58 | needs: push_to_registry 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: check out the repo 62 | uses: actions/checkout@v2 63 | - name: get-npm-version 64 | id: package-version 65 | uses: martinbeentjes/npm-get-version-action@master 66 | - uses: Azure/k8s-set-context@v1 67 | with: 68 | kubeconfig: ${{ secrets.KUBE_CONFIG }} 69 | - uses: Azure/k8s-deploy@v1.4 70 | with: 71 | namespace: 'default' 72 | manifests: kubernetes/deployment.yaml 73 | images: 'ghcr.io/eddiehubcommunity/finder:v${{ steps.package-version.outputs.current-version}}' 74 | kubectl-version: 'latest' 75 | -------------------------------------------------------------------------------- /tests/login.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import { expect, test } from '@playwright/test'; 3 | 4 | test.describe('Login Card', () => { 5 | test('h2 contains desired text', async ({ page }) => { 6 | // navigate to the login page 7 | await page.goto('/login'); 8 | 9 | //check if the h2 element contains the desired text 10 | expect(await page.textContent('h2')).toBe('Login'); 11 | }); 12 | 13 | test('Login Button contains the desired text', async ({ page }) => { 14 | // navigate to the login page 15 | await page.goto('/login'); 16 | 17 | // check if the button contains the text 18 | const githubLoginBtn = await page.locator('data-test-id=github-login-btn'); 19 | expect(await githubLoginBtn.textContent()).toBe('Login with Github'); 20 | }); 21 | 22 | test('Login Button takes you to a Github Login Page', async ({ page }) => { 23 | // navigate to the login page 24 | await page.goto('/login'); 25 | 26 | // click on the login button 27 | await page.locator('data-test-id=github-login-btn').click(); 28 | await page.waitForLoadState('networkidle'); 29 | 30 | // check if its a github url after the redirect 31 | expect(await page.url()).toContain('https://github.com/login'); 32 | }); 33 | }); 34 | 35 | test.describe('Header', () => { 36 | test('Header contains Login Button', async ({ page }) => { 37 | //navigate to the docs page 38 | await page.goto('/docs'); 39 | 40 | //check if the header contains a link that redirects to the login page 41 | await page.locator('data-test-id=login-btn').click(); 42 | await page.waitForLoadState('networkidle'); 43 | 44 | expect(await page.url()).toContain('/login'); 45 | }); 46 | }); 47 | 48 | test.describe('Footer', () => { 49 | test('Footer contains a working Github Link', async ({ page }) => { 50 | // navigate to the login page 51 | await page.goto('/login'); 52 | 53 | // check if the github icon links to a github link 54 | const githubBtn = await page.locator('data-test-id=github-btn'); 55 | const [popup] = await Promise.all([page.waitForEvent('popup'), githubBtn.click()]); 56 | await popup.waitForLoadState(); 57 | 58 | expect(await popup.url()).toContain('https://github.com/EddieHubCommunity'); 59 | }); 60 | 61 | test('Footer contains a working Discord Link', async ({ page }) => { 62 | // navigate to the login page 63 | await page.goto('/login'); 64 | 65 | // check if the discord icon links to a discord link 66 | const discordBtn = await page.locator('data-test-id=discord-btn'); 67 | const [popup] = await Promise.all([page.waitForEvent('popup'), discordBtn.click()]); 68 | await popup.waitForLoadState(); 69 | 70 | expect(await popup.url()).toContain('https://discord.com/invite/jZQs6Wu'); 71 | }); 72 | 73 | test('Footer contains the theme switcher', async ({ page }) => { 74 | // navigate to the login page 75 | await page.goto('/login'); 76 | 77 | // check if the footer contains the theme switcher 78 | expect(await page.locator('data-test-id=theme-switch')).toBeTruthy(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 69 | 70 |
71 |
72 |
73 | 80 |
81 |
82 | performSearch()} /> 83 | 84 |
85 | {#if intersectedArray.length > 0} 86 |
87 | {#each intersectedArray as node} 88 | 89 | {/each} 90 | {#if githubData.pageInfo.hasNextPage} 91 |
92 | 93 |
94 | {/if} 95 |
96 | {:else} 97 |
Unfortunately, there were no issues found.
98 | {/if} 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/EddieHubCommunity/good-first-issue-finder)\ 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/EddieHubCommunity/good-first-issue-finder/blob/main/LICENSE) 3 | 4 | # Good First Issue Finder by EddieHub 5 | 6 | 7 | Good First Issue Finder helps new open source contributors pave their path into the world of open source through good first issues. 8 | 9 | ![eddiehub issue finder](https://user-images.githubusercontent.com/64529217/177034601-fe8dffce-cfac-4f61-889b-e3fe1ab7497d.png) 10 | 11 | 16 | 17 | ## 👨‍💻 Live Version 18 | 19 | Check out the website: [Good First Issue Finder](https://finder.eddiehub.io) 20 | 21 | ## 👇 Prerequisites 22 | 23 | Before installation, please make sure you have already installed the following tools: 24 | 25 | - [Git](https://git-scm.com/downloads) 26 | - [NodeJs](https://nodejs.org/en/download/) 27 | 28 | ## 🛠️ Installation Steps 29 | 30 | 1. Fork the project 31 | 2. Clone the project 32 | ```bash 33 | git clone https://github.com/YOUR_USERNAME/good-first-issue-finder.git 34 | ``` 35 | 3. Navigate to the project directory `cd good-first-issue-finder` 36 | 4. Install dependencies with `npm install` 37 | 5. Create a `.env` file in the root of the project. Then add your GitHub Oauth values (see example in `.env.example` file) 38 | 39 | > **Note:** You need to set up an Oauth App in Github to get the required values. See the documentation [here](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app). 40 | > The Callback Url needs to point to the endpoint `http://localhost:5173/api/authentication/callback` of the baseURL the app should run on. 41 | 42 | callback url 43 | 44 | 6. Run `npm run dev` 45 | 46 | Alternatively, skip all the steps by using [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/EddieHubCommunity/good-first-issue-finder) 47 | 48 | ## 🧪 Testing 49 | 50 | We're using [Playwright](https://playwright.dev/) for running End-To-End Tests. 51 | For running the tests you would need to install the playwright dependencies by running 52 | 53 | ```bash 54 | npx playwright install --with-deps 55 | ``` 56 | 57 | ## 👨‍💻 Contributing 58 | 59 | - Contributions make the open source community such an amazing place to learn, inspire, and create. 60 | - Any contributions you make are **greatly appreciated**. 61 | 64 | 65 | ## 🛡️ License 66 | 67 | Good first issue finder is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 68 | 69 | ## 💪 Thanks to all Contributors 70 | 71 | Thanks a lot for spending your time helping Good first issue finder grow. Thanks a lot! Keep rocking 🍻 72 | 73 | [![Contributors](https://contrib.rocks/image?repo=EddieHubCommunity/good-first-issue-finder)](https://github.com/EddieHubCommunity/good-first-issue-finder/graphs/contributors) 74 | 75 | ## 🙏 Support 76 | 77 | This project needs a ⭐️ from you. Don't forget to leave a star ⭐️. 78 | -------------------------------------------------------------------------------- /static/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | code[class*='language-'], 7 | pre[class*='language-'] { 8 | color: #abb2bf; 9 | background: none; 10 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 11 | text-align: left; 12 | white-space: pre; 13 | word-spacing: normal; 14 | word-break: normal; 15 | word-wrap: normal; 16 | line-height: 1.5; 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | -webkit-hyphens: none; 21 | -moz-hyphens: none; 22 | -ms-hyphens: none; 23 | hyphens: none; 24 | } 25 | 26 | pre[class*='language-']::-moz-selection, 27 | pre[class*='language-'] ::-moz-selection, 28 | code[class*='language-']::-moz-selection, 29 | code[class*='language-'] ::-moz-selection { 30 | text-shadow: none; 31 | background: #383e49; 32 | } 33 | 34 | pre[class*='language-']::selection, 35 | pre[class*='language-'] ::selection, 36 | code[class*='language-']::selection, 37 | code[class*='language-'] ::selection { 38 | text-shadow: none; 39 | background: #9aa2b1; 40 | } 41 | 42 | @media print { 43 | code[class*='language-'], 44 | pre[class*='language-'] { 45 | text-shadow: none; 46 | } 47 | } 48 | /* Code blocks */ 49 | pre[class*='language-'] { 50 | padding: 1em; 51 | margin: 0.5em 0; 52 | overflow: auto; 53 | } 54 | 55 | :not(pre) > code[class*='language-'], 56 | pre[class*='language-'] { 57 | background: #282c34; 58 | } 59 | 60 | /* Inline code */ 61 | :not(pre) > code[class*='language-'] { 62 | padding: 0.1em; 63 | border-radius: 0.3em; 64 | white-space: normal; 65 | } 66 | 67 | .token.comment, 68 | .token.prolog, 69 | .token.doctype, 70 | .token.cdata { 71 | color: #5c6370; 72 | } 73 | 74 | .token.punctuation { 75 | color: #abb2bf; 76 | } 77 | 78 | .token.selector, 79 | .token.tag { 80 | color: #e06c75; 81 | } 82 | 83 | .token.property, 84 | .token.boolean, 85 | .token.number, 86 | .token.constant, 87 | .token.symbol, 88 | .token.attr-name, 89 | .token.deleted { 90 | color: #d19a66; 91 | } 92 | 93 | .token.string, 94 | .token.char, 95 | .token.attr-value, 96 | .token.builtin, 97 | .token.inserted { 98 | color: #98c379; 99 | } 100 | 101 | .token.operator, 102 | .token.entity, 103 | .token.url, 104 | .language-css .token.string, 105 | .style .token.string { 106 | color: #56b6c2; 107 | } 108 | 109 | .token.atrule, 110 | .token.keyword { 111 | color: #c678dd; 112 | } 113 | 114 | .token.function { 115 | color: #61afef; 116 | } 117 | 118 | .token.regex, 119 | .token.important, 120 | .token.variable { 121 | color: #c678dd; 122 | } 123 | 124 | .token.important, 125 | .token.bold { 126 | font-weight: bold; 127 | } 128 | 129 | .token.italic { 130 | font-style: italic; 131 | } 132 | 133 | .token.entity { 134 | cursor: help; 135 | } 136 | 137 | pre.line-numbers { 138 | position: relative; 139 | padding-left: 3.8em; 140 | counter-reset: linenumber; 141 | } 142 | 143 | pre.line-numbers > code { 144 | position: relative; 145 | } 146 | 147 | .line-numbers .line-numbers-rows { 148 | position: absolute; 149 | pointer-events: none; 150 | top: 0; 151 | font-size: 100%; 152 | left: -3.8em; 153 | width: 3em; /* works for line-numbers below 1000 lines */ 154 | letter-spacing: -1px; 155 | border-right: 0; 156 | 157 | -webkit-user-select: none; 158 | -moz-user-select: none; 159 | -ms-user-select: none; 160 | user-select: none; 161 | } 162 | 163 | .line-numbers-rows > span { 164 | pointer-events: none; 165 | display: block; 166 | counter-increment: linenumber; 167 | } 168 | 169 | .line-numbers-rows > span:before { 170 | content: counter(linenumber); 171 | color: #5c6370; 172 | display: block; 173 | padding-right: 0.8em; 174 | text-align: right; 175 | } 176 | -------------------------------------------------------------------------------- /src/lib/components/issue-card.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 |
17 |
18 | github 24 |
25 | 31 | 36 | {issue.title} 38 |
39 |
40 |
41 | 53 |
54 | {#if issue.repository.primaryLanguage} 55 |
61 | 77 |
78 |
79 | {#if !isToggled} 80 |
81 | 82 | Created at: {new Date(issue.createdAt) 83 | .toString() 84 | .replace(/\S+\s(\S+)\s(\d+)\s(\d+)\s.*/, '$2 $1 $3')} 85 | 86 | {#each issue.labels.edges as label} 87 |
90 |
91 |
92 | {#if issue.repository.codeOfConduct} 93 |
96 |
97 | {#if issue.repository.licenseInfo} 98 |
101 |
102 | {/if} 103 |
104 | 105 | 110 | -------------------------------------------------------------------------------- /static/images/open-in-gitpod.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | ## 💥 How to Contribute 4 | 5 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/EddieHubCommunity/good-first-issue-finder/pulls) 6 | [![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.png?v=103)](https://github.com/EddieHubCommunity/) 7 | 8 | - Take a look at the existing [Issues](https://github.com/EddieHubCommunity/good-first-issue-finder/issues) or [create a new issue](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/new/choose)! 9 | - [Fork the Repo](https://github.com/EddieHubCommunity/good-first-issue-finder). Then, create a branch for any issue that you are working on. Finally, commit your work. 10 | - Create a **[Pull Request](https://github.com/EddieHubCommunity/good-first-issue-finder/compare)** (_PR_), which will be promptly reviewed and given suggestions for improvements by the community. 11 | - Add screenshots or screen captures to your Pull Request to help us understand the effects of the changes proposed in your PR. 12 | 13 | --- 14 | 15 | ## ⭐ HOW TO MAKE A PULL REQUEST: 16 | 17 | **1.** Start by making a Fork of the [**good-first-issue-finder**](https://github.com/EddieHubCommunity/good-first-issue-finder) repository. Click on the Fork symbol at the top right corner. 18 | 19 | **2.** Clone your new fork of the repository in the terminal/CLI on your computer with the following command: 20 | 21 | ```bash 22 | git clone https://github.com//good-first-issue-finder 23 | ``` 24 | 25 | **3.** Navigate to the newly created good-first-issue-finder project directory: 26 | 27 | ```bash 28 | cd good-first-issue-finder 29 | ``` 30 | 31 | **4.** Set upstream command: 32 | 33 | ```bash 34 | git remote add upstream https://github.com/EddieHubCommunity/good-first-issue-finder.git 35 | ``` 36 | 37 | **5.** Create a new branch: 38 | 39 | ```bash 40 | git checkout -b YourBranchName 41 | ``` 42 | 43 | **6.** Add secret github token in a .env file: 44 | 45 | -Add a .env file int the root directory of the folder 46 | 47 | -Paste below code inside the .env file with your secret token 48 | 49 | ```bash 50 | GH_TOKEN= 51 | ``` 52 | 53 | > **Note:** A detailed explanation on how to create a GitHub Token can be found [here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) 54 | 55 | **7.** Sync your fork or your local repository with the origin repository: 56 | 57 | - In your forked repository, click on "Fetch upstream" 58 | - Click "Fetch and merge" 59 | 60 | ### Alternatively, Git CLI way to Sync forked repository with origin repository: 61 | 62 | ```bash 63 | git fetch upstream 64 | ``` 65 | 66 | ```bash 67 | git merge upstream/main 68 | ``` 69 | 70 | ### [Github Docs](https://docs.github.com/en/github/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github) for Syncing 71 | 72 | **7.** Make your changes to the source code. 73 | 74 | **8.** Stage your changes and commit: 75 | 76 | ⚠️ **Make sure** not to commit `package.json` or `package-lock.json` file 77 | 78 | ⚠️ **Make sure** not to run the commands `git add .` or `git add *`. Instead, stage your changes for each file/folder 79 | 80 | ```bash 81 | git add 82 | ``` 83 | 84 | ```bash 85 | git commit -m "" 86 | ``` 87 | 88 | **9.** Push your local commits to the remote repository: 89 | 90 | ```bash 91 | git push origin YourBranchName 92 | ``` 93 | 94 | **10.** Create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)! 95 | 96 | **11.** **Congratulations!** You've made your first contribution to [**good-first-issue-finder**](https://github.com/EddieHubCommunity/good-first-issue-finder/graphs/contributors)! 🙌🏼 97 | 98 | **_:trophy: After this, the maintainers will review the PR and will merge it if it helps move the good-first-issue-finder project forward. Otherwise, it will be given constructive feedback and suggestions for the changes needed to add the PR to the codebase._** 99 | 100 | --- 101 | 102 | ## 💥 Issues 103 | 104 | In order to discuss changes, you are welcome to [open an issue](https://github.com/EddieHubCommunity/good-first-issue-finder/issues/new/) about what you would like to contribute. Enhancements are always encouraged and appreciated. 105 | 106 | ## All the best! 🥇 107 | 108 | [![built with love](https://forthebadge.com/images/badges/built-with-love.svg)](eddiehub.org) 109 | 110 | Join the conversation in our [Discord community](http://discord.eddiehub.org) 111 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | community@eddiehub.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | --------------------------------------------------------------------------------