├── src └── app │ ├── py.typed │ ├── cli │ └── __init__.py │ ├── db │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── versions │ │ │ └── __init__.py │ │ └── script.py.mako │ ├── models │ │ ├── team_roles.py │ │ ├── team_tag.py │ │ ├── role.py │ │ ├── tenant.py │ │ ├── __init__.py │ │ ├── team_invitation.py │ │ ├── repo.py │ │ ├── tag.py │ │ ├── user_role.py │ │ ├── icp.py │ │ ├── team.py │ │ ├── oauth_account.py │ │ ├── team_member.py │ │ └── job_post.py │ └── fixtures │ │ └── role.json │ ├── lib │ ├── __init__.py │ ├── scraperapi.py │ ├── pdfshift.py │ ├── crypt.py │ └── utils.py │ ├── server │ ├── __init__.py │ ├── plugins.py │ ├── openapi.py │ └── routers.py │ ├── domain │ ├── __init__.py │ ├── repos │ │ ├── controllers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── repositories.py │ │ ├── dependencies.py │ │ ├── schemas.py │ │ └── services.py │ ├── people │ │ ├── controllers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── repositories.py │ │ └── dependencies.py │ ├── system │ │ ├── urls.py │ │ ├── __init__.py │ │ ├── schemas.py │ │ └── tasks.py │ ├── jobs │ │ ├── controllers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── dependencies.py │ │ ├── services.py │ │ └── repositories.py │ ├── companies │ │ ├── controllers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── repositories.py │ │ └── dependencies.py │ ├── tags │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── repositories.py │ │ ├── services.py │ │ ├── dtos.py │ │ └── dependencies.py │ ├── opportunities │ │ ├── controllers │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── dependencies.py │ │ └── utils.py │ ├── teams │ │ ├── __init__.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── team_invitation.py │ │ ├── urls.py │ │ ├── signals.py │ │ ├── schemas.py │ │ ├── repositories.py │ │ └── dependencies.py │ └── accounts │ │ ├── __init__.py │ │ ├── controllers │ │ ├── __init__.py │ │ └── roles.py │ │ ├── urls.py │ │ ├── utils.py │ │ └── signals.py │ ├── __about__.py │ ├── __init__.py │ ├── config │ ├── __init__.py │ └── constants.py │ ├── __main__.py │ └── asgi.py ├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── lib │ │ ├── __init__.py │ │ ├── test_schema.py │ │ ├── test_settings.py │ │ ├── test_cache.py │ │ └── test_crypt.py │ └── test_cli.py ├── integration │ ├── __init__.py │ ├── test_health.py │ ├── test_access.py │ └── test_tests.py ├── conftest.py └── helpers.py ├── frontend ├── .eslintrc.json ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── error │ │ │ └── page.tsx │ │ ├── (console) │ │ │ ├── icps │ │ │ │ └── page.tsx │ │ │ ├── dashboard │ │ │ │ └── page.tsx │ │ │ ├── opportunities │ │ │ │ ├── [opportunityId] │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── settings │ │ │ │ ├── page.tsx │ │ │ │ └── layout.tsx │ │ │ └── layout.tsx │ │ ├── posthog.js │ │ ├── providers.tsx │ │ ├── PostHogPageView.tsx │ │ ├── (auth) │ │ │ └── auth │ │ │ │ └── confirm │ │ │ │ └── route.ts │ │ └── layout.tsx │ ├── types │ │ ├── location.ts │ │ ├── user.ts │ │ ├── scale.ts │ │ ├── repo.ts │ │ ├── job_post.ts │ │ ├── icp.ts │ │ ├── person.ts │ │ └── company.ts │ ├── lib │ │ └── utils.ts │ ├── components │ │ ├── icons │ │ │ ├── index.ts │ │ │ ├── AppleLogo.tsx │ │ │ ├── IntercomLogo.tsx │ │ │ ├── GooglePlayLogo.tsx │ │ │ ├── NotionLogo.tsx │ │ │ └── G2Logo.tsx │ │ ├── opportunities │ │ │ ├── TabContentHeader.tsx │ │ │ ├── utils.ts │ │ │ ├── data.ts │ │ │ ├── OpportunityDrawer.tsx │ │ │ ├── OpportunityStrengthLabel.tsx │ │ │ └── OpportunityMentions.tsx │ │ ├── PageHeaderRow.tsx │ │ ├── theme-provider.tsx │ │ ├── ErrorMessage.tsx │ │ ├── ui │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── separator.tsx │ │ │ ├── toaster.tsx │ │ │ ├── progress.tsx │ │ │ ├── sonner.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── stage-button.tsx │ │ │ ├── switch.tsx │ │ │ ├── badge.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── popover.tsx │ │ │ ├── toggle.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button-link.tsx │ │ │ ├── alert.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── resizable.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── card.tsx │ │ │ ├── button.tsx │ │ │ └── accordion.tsx │ │ ├── TopNavbar.tsx │ │ ├── EmptySelectionCard.tsx │ │ ├── SideBarNav.tsx │ │ ├── MainNav.tsx │ │ ├── data-table │ │ │ └── data-table-view-options.tsx │ │ └── editor │ │ │ └── editor.tsx │ ├── utils │ │ ├── supabase │ │ │ ├── client.ts │ │ │ ├── server.ts │ │ │ ├── middleware.ts │ │ │ └── auth.ts │ │ ├── nectar │ │ │ └── users.ts │ │ ├── chapter │ │ │ ├── job_post.ts │ │ │ ├── funding.ts │ │ │ └── access.ts │ │ ├── auth.ts │ │ └── github.ts │ ├── TipTap.tsx │ └── middleware.ts ├── public │ └── images │ │ ├── avatar.jpeg │ │ ├── logos │ │ └── apple-logo.svg │ │ └── customIcons │ │ ├── agent-light.svg │ │ ├── agent.svg │ │ ├── funnel.svg │ │ ├── task.svg │ │ └── note.svg ├── next.config.mjs ├── README.md ├── postcss.config.js ├── components.json ├── .gitignore └── tsconfig.json ├── .dockerignore ├── docs ├── _static │ └── badge.png ├── usage │ ├── index.rst │ └── installation.rst └── index.rst ├── assets └── images │ ├── github-cover.png │ ├── chapter-agent-flowchart.png │ └── github-chapter-screenshots.png ├── .prettierrc.json ├── .github ├── dependabot.yaml ├── workflows │ ├── pr-title.yaml │ └── docs.yaml ├── CODEOWNERS └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .prettierignore ├── scripts ├── convert-docs.sh ├── build-docs.py └── pre-build.py ├── .env.testing ├── .env.local.example ├── .env.docker.example ├── docker-compose.infra.yml ├── .pre-commit-config.yaml ├── docker-compose.override.yml └── .eslintrc.cjs /src/app/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/db/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/lib/test_schema.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/db/migrations/versions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .venv 2 | venv 3 | node_modules 4 | __pycache__ 5 | *.pyc 6 | dist/ 7 | tmp/ 8 | -------------------------------------------------------------------------------- /src/app/domain/__init__.py: -------------------------------------------------------------------------------- 1 | """Application Modules.""" 2 | from __future__ import annotations 3 | -------------------------------------------------------------------------------- /docs/_static/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/docs/_static/badge.png -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /assets/images/github-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/assets/images/github-cover.png -------------------------------------------------------------------------------- /docs/usage/index.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | .. toctree:: 6 | :titlesonly: 7 | 8 | installation 9 | -------------------------------------------------------------------------------- /frontend/public/images/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/frontend/public/images/avatar.jpeg -------------------------------------------------------------------------------- /src/app/domain/repos/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .repos import RepoController 2 | 3 | __all__ = ["RepoController"] 4 | -------------------------------------------------------------------------------- /src/app/domain/people/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .persons import PersonController 2 | 3 | __all__ = ["PersonController"] 4 | -------------------------------------------------------------------------------- /src/app/domain/system/urls.py: -------------------------------------------------------------------------------- 1 | SYSTEM_HEALTH: str = "/health" 2 | """Default path for the service health check endpoint.""" 3 | -------------------------------------------------------------------------------- /src/app/domain/jobs/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .job_posts import JobPostController 2 | 3 | __all__ = ["JobPostController"] 4 | -------------------------------------------------------------------------------- /assets/images/chapter-agent-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/assets/images/chapter-agent-flowchart.png -------------------------------------------------------------------------------- /src/app/domain/companies/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .companies import CompanyController 2 | 3 | __all__ = ["CompanyController"] 4 | -------------------------------------------------------------------------------- /src/app/domain/system/__init__.py: -------------------------------------------------------------------------------- 1 | from . import controllers, schemas, tasks 2 | 3 | __all__ = ("controllers", "schemas", "tasks") 4 | -------------------------------------------------------------------------------- /assets/images/github-chapter-screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-gtm/chapter/HEAD/assets/images/github-chapter-screenshots.png -------------------------------------------------------------------------------- /frontend/src/types/location.ts: -------------------------------------------------------------------------------- 1 | export type Location = { 2 | city: string | null 3 | region: string | null 4 | country: string | null 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": false, 6 | "endOfLine": "auto" 7 | } 8 | -------------------------------------------------------------------------------- /src/app/domain/tags/__init__.py: -------------------------------------------------------------------------------- 1 | from . import controllers, dependencies, dtos, services 2 | 3 | __all__ = ["controllers", "services", "dtos", "dependencies"] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /src/app/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-present Cody Fincher 2 | # 3 | # SPDX-License-Identifier: MIT 4 | __version__ = "0.2.0" 5 | -------------------------------------------------------------------------------- /frontend/src/app/error/page.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorMessage } from "@/components/ErrorMessage" 2 | 3 | export default function ErrorPage() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/test_cli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from click.testing import CliRunner 3 | 4 | 5 | @pytest.fixture() 6 | def cli_runner() -> CliRunner: 7 | return CliRunner() 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | templates 2 | scripts 3 | artwork 4 | deploy 5 | docs 6 | *.json 7 | .eslintrc.cjs 8 | postcss.config.cjs 9 | .github 10 | .venv 11 | media 12 | public 13 | dist 14 | .git 15 | -------------------------------------------------------------------------------- /src/app/domain/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | """Job Application Module.""" 2 | from . import controllers, dependencies, schemas, services 3 | 4 | __all__ = ["controllers", "services", "schemas", "dependencies"] 5 | -------------------------------------------------------------------------------- /src/app/domain/opportunities/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .icps import ICPController 2 | from .opportunities import OpportunityController 3 | 4 | __all__ = ["OpportunityController", "ICPController"] 5 | -------------------------------------------------------------------------------- /frontend/src/types/user.ts: -------------------------------------------------------------------------------- 1 | export type User = { 2 | id: string 3 | email: string 4 | name: string 5 | avatarUrl: string 6 | recentlyViewedOpportunityIds: string[] 7 | createdAt: string 8 | } 9 | -------------------------------------------------------------------------------- /src/app/domain/people/__init__.py: -------------------------------------------------------------------------------- 1 | """People Application Module.""" 2 | from . import controllers, dependencies, schemas, services 3 | 4 | __all__ = ["controllers", "services", "schemas", "dependencies"] 5 | -------------------------------------------------------------------------------- /src/app/domain/companies/__init__.py: -------------------------------------------------------------------------------- 1 | """Company Application Module.""" 2 | from . import controllers, dependencies, schemas, services 3 | 4 | __all__ = ["controllers", "services", "schemas", "dependencies"] 5 | -------------------------------------------------------------------------------- /src/app/domain/repos/__init__.py: -------------------------------------------------------------------------------- 1 | """Repo Application Module.""" 2 | from . import controllers, dependencies, schemas, services 3 | 4 | __all__ = ["controllers", "services", "schemas", "dependencies", "tasks"] 5 | -------------------------------------------------------------------------------- /src/app/domain/tags/urls.py: -------------------------------------------------------------------------------- 1 | TAG_LIST = "/api/tags" 2 | TAG_CREATE = "/api/tags" 3 | TAG_UPDATE = "/api/tags/{tag_id:uuid}" 4 | TAG_DELETE = "/api/tags/{tag_id:uuid}" 5 | TAG_DETAILS = "/api/tags/{tag_id:uuid}" 6 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/app/domain/opportunities/__init__.py: -------------------------------------------------------------------------------- 1 | """Opportunity Application Module.""" 2 | from . import controllers, dependencies, schemas, services 3 | 4 | __all__ = ["controllers", "services", "schemas", "dependencies"] 5 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/icps/page.tsx: -------------------------------------------------------------------------------- 1 | import { IcpList } from "@/components/icps/IcpList" 2 | 3 | export default function ICPCriteria() { 4 | return ( 5 | <> 6 | 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /scripts/convert-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CHANGELOG=docs/changelog.rst 4 | 5 | filename="${CHANGELOG%.*}" 6 | echo "Converting $CHANGELOG to $filename.md" 7 | pandoc --wrap=preserve $CHANGELOG -f rst -t markdown -o "$filename".md 8 | -------------------------------------------------------------------------------- /src/app/domain/teams/__init__.py: -------------------------------------------------------------------------------- 1 | """Team Application Module.""" 2 | from . import controllers, dependencies, guards, schemas, services, signals 3 | 4 | __all__ = ["controllers", "guards", "services", "schemas", "dependencies", "signals"] 5 | -------------------------------------------------------------------------------- /src/app/db/models/team_roles.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | 5 | 6 | class TeamRoles(str, Enum): 7 | """Valid Values for Team Roles.""" 8 | 9 | ADMIN = "ADMIN" 10 | MEMBER = "MEMBER" 11 | -------------------------------------------------------------------------------- /frontend/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["img.logo.dev"], // Add the domain you want to allow 5 | }, 6 | output: "standalone", 7 | } 8 | 9 | export default nextConfig 10 | -------------------------------------------------------------------------------- /src/app/domain/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | """User Account domain logic.""" 2 | from app.domain.accounts import controllers, dependencies, guards, schemas, services, signals, urls 3 | 4 | __all__ = ["guards", "services", "controllers", "dependencies", "schemas", "signals", "urls"] 5 | -------------------------------------------------------------------------------- /src/app/domain/teams/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .team_invitation import TeamInvitationController 2 | from .team_member import TeamMemberController 3 | from .teams import TeamController 4 | 5 | __all__ = ["TeamInvitationController", "TeamMemberController", "TeamController"] 6 | -------------------------------------------------------------------------------- /src/app/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-present Cody Fincher 2 | # 3 | # SPDX-License-Identifier: MIT 4 | import multiprocessing 5 | import platform 6 | 7 | if platform.system() == "Darwin": 8 | multiprocessing.set_start_method("fork", force=True) 9 | -------------------------------------------------------------------------------- /src/app/domain/repos/urls.py: -------------------------------------------------------------------------------- 1 | REPO_LIST = "/api/repos" 2 | REPO_DELETE = "/api/repos/{repo_id:uuid}" 3 | REPO_DETAIL = "/api/repos/{repo_id:uuid}" 4 | REPO_UPDATE = "/api/repos/{repo_id:uuid}" 5 | REPO_CREATE = "/api/repos" 6 | REPO_CREATE_FROM_SEARCH_CRITERIA = "/api/repos/from-search-criteria" 7 | -------------------------------------------------------------------------------- /frontend/src/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppleLogo } from "./AppleLogo" 2 | export { default as GongLogo } from "./GongLogo" 3 | export { default as GooglePlayLogo } from "./GooglePlayLogo" 4 | export { default as IntercomLogo } from "./IntercomLogo" 5 | export { default as NotionLogo } from "./NotionLogo" 6 | -------------------------------------------------------------------------------- /src/app/domain/companies/urls.py: -------------------------------------------------------------------------------- 1 | COMPANY_LIST = "/api/companies" 2 | COMPANY_DELETE = "/api/companies/{company_id:uuid}" 3 | COMPANY_DETAIL = "/api/companies/{company_id:uuid}" 4 | COMPANY_UPDATE = "/api/companies/{company_id:uuid}" 5 | COMPANY_CREATE = "/api/companies" 6 | COMPANY_INDEX = "/api/companies/{company_id:uuid}" 7 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Chapter Console App 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | -------------------------------------------------------------------------------- /frontend/src/app/posthog.js: -------------------------------------------------------------------------------- 1 | import { PostHog } from "posthog-node" 2 | 3 | export default function PostHogClient() { 4 | const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, { 5 | host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 6 | flushAt: 1, 7 | flushInterval: 0, 8 | }) 9 | return posthogClient 10 | } 11 | -------------------------------------------------------------------------------- /src/app/domain/people/urls.py: -------------------------------------------------------------------------------- 1 | PERSON_LIST = "/api/persons" 2 | PERSON_DELETE = "/api/persons/{company_id:uuid}" 3 | PERSON_DETAIL = "/api/persons/{company_id:uuid}" 4 | PERSON_UPDATE = "/api/persons/{company_id:uuid}" 5 | PERSON_CREATE = "/api/persons" 6 | PERSON_CREATE_FROM_URL = "/api/persons/from-url" 7 | PERSON_INDEX = "/api/persons/{company_id:uuid}" 8 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "example.com", 11 | port: "", 12 | pathname: "/account123/**", 13 | }, 14 | ], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next" 2 | import { Dashboard } from "@/components/Dashboard" 3 | 4 | export const metadata: Metadata = { 5 | title: "Dashboard: Chapter App", 6 | description: "Lead gen for founders", 7 | } 8 | 9 | export default function DashboardPage() { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /src/app/domain/tags/repositories.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from advanced_alchemy.repository import SQLAlchemyAsyncRepository 4 | 5 | from app.db.models import Tag 6 | 7 | __all__ = ("TagRepository",) 8 | 9 | 10 | class TagRepository(SQLAlchemyAsyncRepository[Tag]): 11 | """Tag Repository.""" 12 | 13 | model_type = Tag 14 | -------------------------------------------------------------------------------- /src/app/domain/accounts/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .access import AccessController 2 | from .roles import RoleController 3 | from .tenant import TenantController 4 | from .user_role import UserRoleController 5 | from .users import UserController 6 | 7 | __all__ = ["AccessController", "UserController", "UserRoleController", "RoleController", "TenantController"] 8 | -------------------------------------------------------------------------------- /tests/unit/lib/test_settings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from app.config import get_settings 4 | 5 | pytestmark = pytest.mark.anyio 6 | 7 | 8 | def test_app_slug() -> None: 9 | """Test app name conversion to slug.""" 10 | settings = get_settings() 11 | settings.app.NAME = "My Application!" 12 | assert settings.app.slug == "my-application" 13 | -------------------------------------------------------------------------------- /src/app/config/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from . import app as plugin_configs 4 | from . import constants 5 | from .base import BASE_DIR, DEFAULT_MODULE_NAME, Settings, get_settings 6 | 7 | __all__ = ( 8 | "Settings", 9 | "get_settings", 10 | "constants", 11 | "plugin_configs", 12 | "DEFAULT_MODULE_NAME", 13 | "BASE_DIR", 14 | ) 15 | -------------------------------------------------------------------------------- /src/app/domain/people/repositories.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from advanced_alchemy.repository import SQLAlchemyAsyncSlugRepository 4 | 5 | from app.db.models import Person 6 | 7 | __all__ = ("PersonRepository",) 8 | 9 | 10 | class PersonRepository(SQLAlchemyAsyncSlugRepository[Person]): 11 | """Person Repository.""" 12 | 13 | model_type = Person 14 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/opportunities/[opportunityId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { OpportunityFull } from "@/components/opportunities/OpportunityFull" 2 | 3 | export default function OpportunityFullPage({ 4 | params, 5 | }: { 6 | params: { opportunityId: string } 7 | }) { 8 | return ( 9 | <> 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/domain/companies/repositories.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from advanced_alchemy.repository import SQLAlchemyAsyncSlugRepository 4 | 5 | from app.db.models import Company 6 | 7 | __all__ = ("CompanyRepository",) 8 | 9 | 10 | class CompanyRepository(SQLAlchemyAsyncSlugRepository[Company]): 11 | """Company Repository.""" 12 | 13 | model_type = Company 14 | -------------------------------------------------------------------------------- /src/app/domain/repos/repositories.py: -------------------------------------------------------------------------------- 1 | """Repo repositories.""" 2 | from __future__ import annotations 3 | 4 | from advanced_alchemy.repository import SQLAlchemyAsyncSlugRepository 5 | 6 | from app.db.models import Repo 7 | 8 | __all__ = ("RepoRepository",) 9 | 10 | 11 | class RepoRepository(SQLAlchemyAsyncSlugRepository[Repo]): 12 | """Repo Repository.""" 13 | 14 | model_type = Repo 15 | -------------------------------------------------------------------------------- /src/app/db/fixtures/role.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "slug": "application-access", 4 | "name": "Application Access", 5 | "description": "Default role required for access. This role allows you to query and access the application." 6 | }, 7 | { 8 | "slug": "superuser", 9 | "name": "Superuser", 10 | "description": "Allows superuser access to the application." 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /src/app/domain/jobs/urls.py: -------------------------------------------------------------------------------- 1 | JOBS_LIST = "/api/jobs" 2 | JOBS_DELETE = "/api/jobs/{job_post_id:uuid}" 3 | JOBS_DETAIL = "/api/jobs/{job_post_id:uuid}" 4 | JOBS_PDF = "/api/jobs/{job_post_id:uuid}/pdf" 5 | JOBS_UPDATE = "/api/jobs/{job_post_id:uuid}" 6 | JOBS_UPDATE_ADD_PDF = "/api/jobs/{job_post_id:uuid}/pdf" 7 | JOBS_CREATE = "/api/jobs" 8 | JOBS_CREATE_FROM_URL = "/api/jobs/from-url" 9 | JOBS_INDEX = "/api/jobs/{job_post_id:uuid}" 10 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.env.testing: -------------------------------------------------------------------------------- 1 | # App 2 | SECRET_KEY='secret-key' 3 | 4 | # Cache 5 | REDIS_URL=redis://localhost:6397/0 6 | 7 | SAQ_USE_SERVER_LIFESPAN=False # don't use with docker. 8 | SAQ_WEB_ENABLED=True 9 | SAQ_BACKGROUND_WORKERS=1 10 | SAQ_CONCURRENCY=1 11 | 12 | APP_S3_BUCKET_NAME= 13 | 14 | # 3rd party API keys 15 | LOGO_DEV_TOKEN= 16 | SCRAPERAPI_API_KEY= 17 | PDFSHIFT_API_KEY= 18 | OPENAI_MODEL_NAME= 19 | OPENAI_API_KEY= 20 | PDL_API_KEY= 21 | PB_API_KEY= 22 | -------------------------------------------------------------------------------- /frontend/src/types/scale.ts: -------------------------------------------------------------------------------- 1 | export const ScaleLabel: { [key: string]: { label: string; color: string } } = { 2 | Low: { label: "Low", color: "bg-card" }, 3 | Medium: { label: "Medium", color: "bg-card" }, 4 | High: { label: "High", color: "bg-card" }, 5 | } 6 | 7 | export enum Scale { 8 | LOW = "Low", 9 | MEDIUM = "Medium", 10 | HIGH = "High", 11 | } 12 | 13 | export enum Score { 14 | EXCELLENT = "Excellent", 15 | GREAT = "Great", 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yaml: -------------------------------------------------------------------------------- 1 | name: "Lint PR Title" 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | pull-requests: read 12 | 13 | jobs: 14 | main: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: amannn/action-semantic-pull-request@v5 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /src/app/domain/teams/urls.py: -------------------------------------------------------------------------------- 1 | TEAM_LIST = "/api/teams" 2 | TEAM_DELETE = "/api/teams/{team_id:uuid}" 3 | TEAM_DETAIL = "/api/teams/{team_id:uuid}" 4 | TEAM_UPDATE = "/api/teams/{team_id:uuid}" 5 | TEAM_CREATE = "/api/teams" 6 | TEAM_INDEX = "/api/teams/{team_id:uuid}" 7 | TEAM_INVITATION_LIST = "/api/teams/{team_id:uuid}/invitations" 8 | TEAM_ADD_MEMBER = "/api/teams/{team_id:uuid}/members/add" 9 | TEAM_REMOVE_MEMBER = "/api/teams/{team_id:uuid}/members/remove" 10 | -------------------------------------------------------------------------------- /frontend/src/utils/supabase/client.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserClient } from "@supabase/ssr" 2 | 3 | export function createClient() { 4 | return createBrowserClient( 5 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! 7 | ) 8 | } 9 | 10 | export async function getUserAccessToken() { 11 | const supabase = createClient() 12 | const session = await supabase.auth.getSession() 13 | return session.data.session?.access_token 14 | } 15 | -------------------------------------------------------------------------------- /src/app/db/models/team_tag.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Final 4 | 5 | from advanced_alchemy.base import orm_registry 6 | from sqlalchemy import Column, ForeignKey, Table 7 | 8 | team_tag: Final[Table] = Table( 9 | "team_tag", 10 | orm_registry.metadata, 11 | Column("team_id", ForeignKey("team.id", ondelete="CASCADE"), primary_key=True), 12 | Column("tag_id", ForeignKey("tag.id", ondelete="CASCADE"), primary_key=True), 13 | ) 14 | -------------------------------------------------------------------------------- /src/app/domain/tags/services.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService 4 | 5 | from app.db.models import Tag 6 | 7 | from .repositories import TagRepository 8 | 9 | __all__ = ("TagService",) 10 | 11 | 12 | class TagService(SQLAlchemyAsyncRepositoryService[Tag]): 13 | """Handles basic lookup operations for an Tag.""" 14 | 15 | repository_type = TagRepository 16 | match_fields = ["name"] 17 | -------------------------------------------------------------------------------- /src/app/domain/teams/controllers/team_invitation.py: -------------------------------------------------------------------------------- 1 | """User Account Controllers.""" 2 | from __future__ import annotations 3 | 4 | from litestar import Controller 5 | from litestar.di import Provide 6 | 7 | from app.domain.teams.dependencies import provide_team_invitations_service 8 | 9 | 10 | class TeamInvitationController(Controller): 11 | """Team Invitations.""" 12 | 13 | tags = ["Teams"] 14 | dependencies = {"team_invitations_service": Provide(provide_team_invitations_service)} 15 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/opportunities/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next" 2 | import { PageHeaderRow } from "@/components/PageHeaderRow" 3 | import { OpportunitiesMain } from "@/components/opportunities/OpportunitiesMain" 4 | 5 | export const metadata: Metadata = { 6 | title: "Opportunities: Chapter App", 7 | description: "Lead gen for founders", 8 | } 9 | 10 | export default function FunnelPage() { 11 | return ( 12 | <> 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/components/opportunities/TabContentHeader.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | 5 | interface TabContentHeaderProps extends React.HTMLAttributes {} 6 | 7 | function TabContentHeader({ children, ...props }: TabContentHeaderProps) { 8 | return ( 9 |
10 | 13 |
14 | ) 15 | } 16 | 17 | export { TabContentHeader } 18 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owner settings for `litestar fullstack` 2 | # @maintainers should be assigned to all reviews. 3 | # Most specific assignment takes precedence though, so if you add a more specific thing than the `*` glob, you must also add @maintainers 4 | # For more info about code owners see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-example 5 | 6 | # Global Assignment 7 | * @cofin @litestar-org/maintainers @litestar-org/members 8 | -------------------------------------------------------------------------------- /frontend/src/utils/nectar/users.ts: -------------------------------------------------------------------------------- 1 | import { type User } from "@/types/user" 2 | 3 | const NECTAR_API_BASE = "https://api.nectar.run/api" 4 | 5 | export async function getUserProfile(token: string) { 6 | const response = await fetch(NECTAR_API_BASE + "/me", { 7 | method: "GET", 8 | headers: { 9 | Authorization: `Bearer ${token}`, 10 | }, 11 | }) 12 | if (!response.ok) { 13 | throw new Error("Failed to fetch data") 14 | } 15 | const data = await response.json() 16 | const user = data as User 17 | return user 18 | } 19 | -------------------------------------------------------------------------------- /src/app/domain/system/schemas.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Literal 3 | 4 | from app.__about__ import __version__ as current_version 5 | from app.config.base import get_settings 6 | 7 | __all__ = ("SystemHealth",) 8 | 9 | settings = get_settings() 10 | 11 | 12 | @dataclass 13 | class SystemHealth: 14 | database_status: Literal["online", "offline"] 15 | cache_status: Literal["online", "offline"] 16 | worker_status: Literal["online", "offline"] 17 | app: str = settings.app.NAME 18 | version: str = current_version 19 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /frontend/src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import posthog from "posthog-js" 4 | import { PostHogProvider } from "posthog-js/react" 5 | 6 | if (typeof window !== "undefined") { 7 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { 8 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 9 | capture_pageview: false, // Disable automatic pageview capture, as we capture manually 10 | }) 11 | } 12 | 13 | export function PHProvider({ children }: { children: React.ReactNode }) { 14 | return {children} 15 | } 16 | -------------------------------------------------------------------------------- /src/app/domain/opportunities/urls.py: -------------------------------------------------------------------------------- 1 | OPPORTUNITY_LIST = "/api/opportunities" 2 | OPPORTUNITY_DELETE = "/api/opportunities/{opportunity_id:uuid}" 3 | OPPORTUNITY_DETAIL = "/api/opportunities/{opportunity_id:uuid}" 4 | OPPORTUNITY_UPDATE = "/api/opportunities/{opportunity_id:uuid}" 5 | OPPORTUNITY_CREATE = "/api/opportunities" 6 | OPPORTUNITY_SCAN_FOR = "/api/opportunities/scan-for" 7 | OPPORTUNITY_INDEX = "/api/opportunities/{opportunity_id:uuid}" 8 | ICP_LIST = "/api/icps" 9 | ICP_DETAIL = "/api/icps/{icp_id:uuid}" 10 | ICP_UPDATE = "/api/icps/{icp_id:uuid}" 11 | ICP_CREATE = "/api/icps" 12 | -------------------------------------------------------------------------------- /tests/integration/test_health.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from httpx import AsyncClient 3 | 4 | from app.__about__ import __version__ 5 | 6 | pytestmark = pytest.mark.anyio 7 | 8 | 9 | async def test_health(client: AsyncClient) -> None: 10 | response = await client.get("/health") 11 | assert response.status_code == 200 12 | 13 | expected = { 14 | "database_status": "online", 15 | "cache_status": "online", 16 | "worker_status": "online", 17 | "app": "app", 18 | "version": __version__, 19 | } 20 | 21 | assert response.json() == expected 22 | -------------------------------------------------------------------------------- /frontend/src/TipTap.tsx: -------------------------------------------------------------------------------- 1 | // src/Tiptap.tsx 2 | import { 3 | EditorProvider, 4 | FloatingMenu, 5 | BubbleMenu, 6 | EditorContent, 7 | useEditor, 8 | } from "@tiptap/react" 9 | import StarterKit from "@tiptap/starter-kit" 10 | 11 | // define your extension array 12 | const extensions = [StarterKit] 13 | 14 | const content = "

Hello World!

" 15 | 16 | const Tiptap = () => { 17 | const editor = useEditor({ 18 | extensions: [StarterKit], 19 | content: "

Hello World 2!

", 20 | }) 21 | return 22 | } 23 | 24 | export default Tiptap 25 | -------------------------------------------------------------------------------- /frontend/src/components/PageHeaderRow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Link from "next/link" 4 | import { Button } from "@/components/ui/button" 5 | import * as LucideIcons from "lucide-react" 6 | 7 | interface PageHeaderProps { 8 | title?: string 9 | action?: string 10 | } 11 | 12 | export function PageHeaderRow({ title, action }: PageHeaderProps) { 13 | return ( 14 |
15 |

{title}

16 | {action && } 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { type ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function Providers({ children }: { children: React.ReactNode }) { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | 15 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 16 | return {children} 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/lib/test_cache.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from litestar.config.response_cache import default_cache_key_builder 3 | from litestar.testing import RequestFactory 4 | 5 | from app.server.builder import ApplicationConfigurator 6 | 7 | pytestmark = pytest.mark.anyio 8 | 9 | 10 | def test_cache_key_builder(monkeypatch: "pytest.MonkeyPatch") -> None: 11 | monkeypatch.setattr(ApplicationConfigurator, "app_slug", "the-slug") 12 | request = RequestFactory().get("/test") 13 | default_cache_key = default_cache_key_builder(request) 14 | assert ApplicationConfigurator()._cache_key_builder(request) == f"the-slug:{default_cache_key}" 15 | -------------------------------------------------------------------------------- /src/app/server/plugins.py: -------------------------------------------------------------------------------- 1 | from advanced_alchemy.extensions.litestar import SQLAlchemyPlugin 2 | from litestar.plugins.structlog import StructlogPlugin 3 | from litestar_granian import GranianPlugin 4 | from litestar_saq import SAQPlugin 5 | from litestar_vite import VitePlugin 6 | 7 | from app.config import app as config 8 | from app.server.builder import ApplicationConfigurator 9 | 10 | structlog = StructlogPlugin(config=config.log) 11 | vite = VitePlugin(config=config.vite) 12 | saq = SAQPlugin(config=config.saq) 13 | alchemy = SQLAlchemyPlugin(config=config.alchemy) 14 | granian = GranianPlugin() 15 | app_config = ApplicationConfigurator() 16 | -------------------------------------------------------------------------------- /frontend/src/components/opportunities/utils.ts: -------------------------------------------------------------------------------- 1 | import { Active, DataRef, Over } from "@dnd-kit/core" 2 | import { ColumnDragData } from "./BoardColumn" 3 | import { ItemDragData } from "./ItemCard" 4 | 5 | type DraggableData = ColumnDragData | ItemDragData 6 | 7 | export function hasDraggableData( 8 | entry: T | null | undefined 9 | ): entry is T & { 10 | data: DataRef 11 | } { 12 | if (!entry) { 13 | return false 14 | } 15 | 16 | const data = entry.data.current 17 | 18 | if (data?.type === "Column" || data?.type === "Item") { 19 | return true 20 | } 21 | 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/components/opportunities/data.ts: -------------------------------------------------------------------------------- 1 | import { toTitleCase } from "@/utils/misc" 2 | import { OpportunityStage } from "@/types/opportunity" 3 | import { getOpportunities } from "@/utils/chapter/opportunity" 4 | 5 | import type { Column } from "./BoardColumn" 6 | 7 | export async function getColumns() { 8 | let stages: Column[] = [] 9 | for (let stage in OpportunityStage) { 10 | const value = OpportunityStage[stage as keyof typeof OpportunityStage] 11 | stages.push({ id: value, title: toTitleCase(stage) }) 12 | } 13 | return stages 14 | } 15 | 16 | export async function getRecords() { 17 | return await getOpportunities() 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import { ServerCrash } from "lucide-react" 2 | 3 | export function ErrorMessage() { 4 | return ( 5 |
6 |
7 | 8 |

2. Something went wrong

9 |

10 | Please contact support, we'll sort it out for you! 11 |

12 |
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/app/server/openapi.py: -------------------------------------------------------------------------------- 1 | from litestar.openapi.config import OpenAPIConfig 2 | from litestar.openapi.plugins import ScalarRenderPlugin 3 | 4 | from app.__about__ import __version__ as current_version 5 | from app.config import get_settings 6 | from app.domain.accounts.guards import auth 7 | 8 | settings = get_settings() 9 | config = OpenAPIConfig( 10 | title=settings.app.NAME, 11 | version=current_version, 12 | components=[auth.openapi_components], 13 | security=[auth.security_requirement], 14 | use_handler_docstrings=True, 15 | render_plugins=[ScalarRenderPlugin()], 16 | ) 17 | """OpenAPI config for app. See OpenAPISettings for configuration.""" 18 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { Separator } from "@/components/ui/separator" 2 | import Image from "next/image" 3 | 4 | export default function AccountSettings() { 5 | return ( 6 |
7 |
8 |
9 | Inbox 16 |
17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/app/(console)/settings/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Separator } from "@/components/ui/separator" 4 | import { SidebarNav } from "@/components/SideBarNav" 5 | 6 | import { 7 | LayoutDashboard, 8 | CheckSquareIcon, 9 | Loader, 10 | ListTodo, 11 | LineChart, 12 | Inbox, 13 | ChevronLeftIcon, 14 | Layers2Icon, 15 | } from "lucide-react" 16 | 17 | const sidebarNavItems = [ 18 | { 19 | title: "Prospect agent", 20 | href: "/settings/account", 21 | }, 22 | ] 23 | 24 | interface SettingsLayoutProps { 25 | children: React.ReactNode 26 | } 27 | 28 | export default function SettingsLayout({ children }: SettingsLayoutProps) { 29 | return <>{children} 30 | } 31 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/app/domain/accounts/controllers/roles.py: -------------------------------------------------------------------------------- 1 | """Role Routes.""" 2 | from __future__ import annotations 3 | 4 | from litestar import Controller 5 | from litestar.di import Provide 6 | 7 | from app.domain.accounts.dependencies import provide_roles_service 8 | from app.domain.accounts.guards import requires_superuser 9 | from app.domain.accounts.services import RoleService 10 | 11 | 12 | class RoleController(Controller): 13 | """Handles the adding and removing of new Roles.""" 14 | 15 | tags = ["Roles"] 16 | guards = [requires_superuser] 17 | dependencies = { 18 | "roles_service": Provide(provide_roles_service), 19 | } 20 | signature_namespace = {"RoleService": RoleService} 21 | -------------------------------------------------------------------------------- /src/app/domain/repos/dependencies.py: -------------------------------------------------------------------------------- 1 | """Repo Controllers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from app.domain.repos.services import RepoService 8 | 9 | __all__ = ("provide_repos_service",) 10 | 11 | 12 | if TYPE_CHECKING: 13 | from collections.abc import AsyncGenerator 14 | 15 | from sqlalchemy.ext.asyncio import AsyncSession 16 | 17 | 18 | async def provide_repos_service(db_session: AsyncSession) -> AsyncGenerator[RepoService, None]: 19 | """Construct repository and service objects for the request.""" 20 | async with RepoService.new( 21 | session=db_session, 22 | load=[], 23 | ) as service: 24 | yield service 25 | -------------------------------------------------------------------------------- /src/app/domain/tags/dtos.py: -------------------------------------------------------------------------------- 1 | from advanced_alchemy.extensions.litestar.dto import SQLAlchemyDTO 2 | 3 | from app.db.models import Tag 4 | from app.lib import dto 5 | 6 | __all__ = ["TagCreateDTO", "TagDTO", "TagUpdateDTO"] 7 | 8 | 9 | # database model 10 | 11 | 12 | class TagDTO(SQLAlchemyDTO[Tag]): 13 | config = dto.config(max_nested_depth=0, exclude={"created_at", "updated_at", "teams"}) 14 | 15 | 16 | class TagCreateDTO(SQLAlchemyDTO[Tag]): 17 | config = dto.config(max_nested_depth=0, exclude={"id", "created_at", "updated_at", "teams"}) 18 | 19 | 20 | class TagUpdateDTO(SQLAlchemyDTO[Tag]): 21 | config = dto.config(max_nested_depth=0, exclude={"id", "created_at", "updated_at", "teams"}, partial=True) 22 | -------------------------------------------------------------------------------- /src/app/lib/scraperapi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import httpx 4 | 5 | scraper_api_key = os.environ["SCRAPERAPI_API_KEY"] 6 | 7 | 8 | async def extract_url_content(url: str, render: bool = False, timeout: float = 60.0) -> str: 9 | """Extracts URL content using a 3rd party service""" 10 | params = { 11 | "api_key": scraper_api_key, 12 | "url": url, 13 | "follow_redirect": "false", 14 | "country_code": "us", 15 | "device_type": "desktop", 16 | "render": str(render).lower(), 17 | } 18 | 19 | async with httpx.AsyncClient() as client: 20 | response = await client.get("https://api.scraperapi.com", params=params, timeout=timeout) 21 | return response.text 22 | -------------------------------------------------------------------------------- /src/app/domain/people/dependencies.py: -------------------------------------------------------------------------------- 1 | """People Controllers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from app.domain.people.services import PersonService 8 | 9 | __all__ = ("provide_persons_service",) 10 | 11 | 12 | if TYPE_CHECKING: 13 | from collections.abc import AsyncGenerator 14 | 15 | from sqlalchemy.ext.asyncio import AsyncSession 16 | 17 | 18 | async def provide_persons_service(db_session: AsyncSession) -> AsyncGenerator[PersonService, None]: 19 | """Construct repository and service objects for the request.""" 20 | async with PersonService.new( 21 | session=db_session, 22 | load=[], 23 | ) as service: 24 | yield service 25 | -------------------------------------------------------------------------------- /src/app/domain/jobs/dependencies.py: -------------------------------------------------------------------------------- 1 | """Job Post Controllers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from app.domain.jobs.services import JobPostService 8 | 9 | __all__ = ("provide_job_posts_service",) 10 | 11 | 12 | if TYPE_CHECKING: 13 | from collections.abc import AsyncGenerator 14 | 15 | from sqlalchemy.ext.asyncio import AsyncSession 16 | 17 | 18 | async def provide_job_posts_service(db_session: AsyncSession) -> AsyncGenerator[JobPostService, None]: 19 | """Construct repository and service objects for the request.""" 20 | async with JobPostService.new( 21 | session=db_session, 22 | load=[], 23 | ) as service: 24 | yield service 25 | -------------------------------------------------------------------------------- /src/app/domain/companies/dependencies.py: -------------------------------------------------------------------------------- 1 | """User Account Controllers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from app.domain.companies.services import CompanyService 8 | 9 | __all__ = ("provide_companies_service",) 10 | 11 | 12 | if TYPE_CHECKING: 13 | from collections.abc import AsyncGenerator 14 | 15 | from sqlalchemy.ext.asyncio import AsyncSession 16 | 17 | 18 | async def provide_companies_service(db_session: AsyncSession) -> AsyncGenerator[CompanyService, None]: 19 | """Construct repository and service objects for the request.""" 20 | async with CompanyService.new( 21 | session=db_session, 22 | load=[], 23 | ) as service: 24 | yield service 25 | -------------------------------------------------------------------------------- /frontend/src/types/repo.ts: -------------------------------------------------------------------------------- 1 | export type Repo = { 2 | id: string 3 | name: string 4 | url: string 5 | htmlUrl: string 6 | description: string | null 7 | language: string | null 8 | createdAt: Date | null 9 | updatedAt: Date | null 10 | } 11 | 12 | export type RepoMetadata = { 13 | id: string 14 | name: string 15 | url: string 16 | htmlUrl: string 17 | description: string | null 18 | language: string | null 19 | stargazersCount: number | null 20 | watchersCount: number | null 21 | forksCount: number | null 22 | topics: string[] | null 23 | createdAt: Date | null 24 | updatedAt: Date | null 25 | pushedAt: Date | null 26 | lastReleasePublishedAt: Date | null 27 | releasePublishAverageFrequencyPerWeek: number 28 | } 29 | -------------------------------------------------------------------------------- /src/app/domain/accounts/urls.py: -------------------------------------------------------------------------------- 1 | ACCOUNT_LOGIN = "/api/access/login" 2 | ACCOUNT_LOGOUT = "/api/access/logout" 3 | ACCOUNT_REGISTER = "/api/access/signup" 4 | ACCOUNT_PROFILE = "/api/me" 5 | ACCOUNT_PROFILE_UPDATE = "/api/me" 6 | ACCOUNT_LIST = "/api/users" 7 | ACCOUNT_DELETE = "/api/users/{user_id:uuid}" 8 | ACCOUNT_DETAIL = "/api/users/{user_id:uuid}" 9 | ACCOUNT_UPDATE = "/api/users/{user_id:uuid}" 10 | ACCOUNT_CREATE = "/api/users" 11 | ACCOUNT_ASSIGN_ROLE = "/api/roles/{role_slug:str}/assign" 12 | ACCOUNT_REVOKE_ROLE = "/api/roles/{role_slug:str}/revoke" 13 | ACCOUNT_TENANT_DELETE = "/api/tenants" 14 | ACCOUNT_TENANT_DETAIL = "/api/tenants/{tenant_id:uuid}" 15 | ACCOUNT_TENANT_UPDATE = "/api/tenants/{tenant_id:uuid}" 16 | ACCOUNT_TENANT_CREATE = "/api/tenants" 17 | -------------------------------------------------------------------------------- /src/app/db/models/role.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from advanced_alchemy.base import SlugKey, UUIDAuditBase 6 | from sqlalchemy.orm import Mapped, mapped_column, relationship 7 | 8 | if TYPE_CHECKING: 9 | from .user_role import UserRole 10 | 11 | 12 | class Role(UUIDAuditBase, SlugKey): 13 | """Role.""" 14 | 15 | __tablename__ = "role" 16 | 17 | name: Mapped[str] = mapped_column(unique=True) 18 | description: Mapped[str | None] 19 | # ----------- 20 | # ORM Relationships 21 | # ------------ 22 | users: Mapped[list[UserRole]] = relationship( 23 | back_populates="role", 24 | cascade="all, delete", 25 | lazy="noload", 26 | viewonly=True, 27 | ) 28 | -------------------------------------------------------------------------------- /frontend/src/app/PostHogPageView.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { usePathname, useSearchParams } from "next/navigation" 4 | import { useEffect } from "react" 5 | import { usePostHog } from "posthog-js/react" 6 | 7 | export default function PostHogPageView(): null { 8 | const pathname = usePathname() 9 | const searchParams = useSearchParams() 10 | const posthog = usePostHog() 11 | // Track pageviews 12 | useEffect(() => { 13 | if (pathname && posthog) { 14 | let url = window.origin + pathname 15 | if (searchParams.toString()) { 16 | url = url + `?${searchParams.toString()}` 17 | } 18 | posthog.capture("$pageview", { 19 | $current_url: url, 20 | }) 21 | } 22 | }, [pathname, searchParams, posthog]) 23 | 24 | return null 25 | } 26 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # App 2 | SECRET_KEY='secret-key' 3 | LITESTAR_DEBUG=true 4 | LITESTAR_HOST=0.0.0.0 5 | LITESTAR_PORT=8089 6 | APP_URL=http://localhost:${LITESTAR_PORT} 7 | 8 | LOG_LEVEL=10 9 | # Database 10 | DATABASE_ECHO=true 11 | DATABASE_ECHO_POOL=true 12 | DATABASE_POOL_DISABLE=false 13 | DATABASE_POOL_MAX_OVERFLOW=5 14 | DATABASE_POOL_SIZE=5 15 | DATABASE_POOL_TIMEOUT=30 16 | DATABASE_URL=postgresql+asyncpg://chapter:chapter@localhost:5432/chapter 17 | 18 | REDIS_URL=redis://localhost:6379/0 19 | 20 | # Worker 21 | SAQ_USE_SERVER_LIFESPAN=True 22 | SAQ_WEB_ENABLED=True 23 | SAQ_BACKGROUND_WORKERS=1 24 | SAQ_CONCURRENCY=1 25 | 26 | # 3rd party API keys 27 | LOGO_DEV_TOKEN= 28 | SCRAPERAPI_API_KEY= 29 | PDFSHIFT_API_KEY= 30 | OPENAI_MODEL_NAME= 31 | OPENAI_API_KEY= 32 | PDL_API_KEY= 33 | PB_API_KEY= 34 | -------------------------------------------------------------------------------- /frontend/public/images/logos/apple-logo.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/types/job_post.ts: -------------------------------------------------------------------------------- 1 | import { type Location } from "@/types/location" 2 | import { type Company } from "@/types/company" 3 | import { Scale } from "@/types/scale" 4 | 5 | export type Tool = { 6 | name: string 7 | certainty: Scale 8 | } 9 | 10 | export type Process = { 11 | name: string 12 | } 13 | 14 | export type JobPost = { 15 | id: string 16 | title: number | null 17 | location: Location | null 18 | seniority_level: string | null 19 | employment_type: string | null 20 | job_functions: string[] | null 21 | url: string | null 22 | apply_url: string | null 23 | total_applicants: number | null 24 | external_id: string | null 25 | tools: Tool[] | null | null 26 | processes: Process[] | null | null 27 | company: Company | null 28 | createdAt: Date | null 29 | updatedAt: Date | null 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-normal leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /frontend/public/images/customIcons/agent-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |