├── .npmrc ├── packages ├── ui │ ├── src │ │ ├── hooks │ │ │ ├── .gitkeep │ │ │ └── use-mobile.ts │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── aspect-ratio.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── sonner.tsx │ │ │ ├── label.tsx │ │ │ ├── separator.tsx │ │ │ ├── textarea.tsx │ │ │ ├── progress.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── input.tsx │ │ │ ├── switch.tsx │ │ │ ├── avatar.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── toggle.tsx │ │ │ ├── badge.tsx │ │ │ ├── popover.tsx │ │ │ ├── alert.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── tabs.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── slider.tsx │ │ │ ├── accordion.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── table.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── calendar.tsx │ │ │ ├── pagination.tsx │ │ │ ├── dialog.tsx │ │ │ ├── form.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── sheet.tsx │ │ │ ├── command.tsx │ │ │ ├── carousel.tsx │ │ │ ├── select.tsx │ │ │ └── navigation-menu.tsx │ │ └── lib │ │ │ └── utils.ts │ ├── tsconfig.json │ ├── components.json │ └── package.json ├── database │ ├── .gitignore │ ├── src │ │ ├── schema │ │ │ ├── index.ts │ │ │ ├── main.ts │ │ │ └── auth.ts │ │ ├── index.ts │ │ └── base.json │ ├── migrations │ │ ├── 0002_dusty_mephistopheles.sql │ │ ├── 0001_watery_satana.sql │ │ ├── meta │ │ │ └── _journal.json │ │ └── 0000_daffy_ego.sql │ ├── drizzle.config.ts │ ├── tsconfig.json │ ├── db-local.sh │ └── package.json ├── constants │ ├── src │ │ ├── index.ts │ │ ├── appInfo.ts │ │ └── tailwindConfig.ts │ ├── tsconfig.json │ └── package.json ├── typescript-config │ ├── README.md │ ├── package.json │ ├── react-library.json │ └── base.json └── email │ ├── tsconfig.json │ ├── readme.md │ ├── package.json │ └── emails │ ├── ResetPassword.tsx │ └── VerificationEmail.tsx ├── apps ├── web │ ├── src │ │ ├── pages │ │ │ ├── forgot-password.astro │ │ │ ├── settings │ │ │ │ ├── security.astro │ │ │ │ ├── sessions.astro │ │ │ │ ├── billing │ │ │ │ │ └── index.astro │ │ │ │ └── user.astro │ │ │ ├── dashboard │ │ │ │ ├── record.astro │ │ │ │ ├── index.astro │ │ │ │ ├── transcripts │ │ │ │ │ ├── index.astro │ │ │ │ │ └── [id].astro │ │ │ │ └── file-upload.astro │ │ │ ├── index.astro │ │ │ ├── verify-email.astro │ │ │ ├── reset-password.astro │ │ │ ├── login.astro │ │ │ └── register.astro │ │ ├── components │ │ │ ├── PagesVerifyPhone.tsx │ │ │ ├── DashboardHeader.tsx │ │ │ ├── PagesDashboard.tsx │ │ │ ├── PagesResetPassword.tsx │ │ │ ├── PagesHome.tsx │ │ │ ├── PagesVerifyEmail.tsx │ │ │ └── PagesDashboardTranscripts.tsx │ │ ├── layouts │ │ │ ├── Layout.astro │ │ │ ├── LayoutDashboard.astro │ │ │ └── LayoutBase.astro │ │ ├── lib │ │ │ ├── apiClient.ts │ │ │ └── authClient.ts │ │ ├── env.d.ts │ │ ├── utils │ │ │ └── deriveUserData.ts │ │ ├── middleware.ts │ │ └── assets │ │ │ ├── background.svg │ │ │ └── astro.svg │ ├── public │ │ ├── .assetsignore │ │ └── favicon.svg │ ├── .env.example │ ├── .vscode │ │ ├── settings.json │ │ ├── extensions.json │ │ └── launch.json │ ├── .gitignore │ ├── components.json │ ├── tsconfig.json │ ├── astro.config.mjs │ ├── wrangler.jsonc │ ├── package.json │ └── README.md └── api │ ├── README.md │ ├── tsconfig.build.json │ ├── src │ ├── lib │ │ ├── database.ts │ │ ├── auth │ │ │ └── hooks │ │ │ │ └── emails.tsx │ │ └── auth.ts │ ├── middleware │ │ └── allowBrowser.ts │ ├── client.ts │ ├── types │ │ └── AppEnv.ts │ ├── index.ts │ ├── routes │ │ └── notifications.ts │ └── durables │ │ └── NotificationWebsocketServer.ts │ ├── .dev.vars.example │ ├── tsconfig.json │ ├── .gitignore │ ├── package.json │ └── wrangler.jsonc ├── pnpm-workspace.yaml ├── README.md ├── .gitignore ├── package.json └── .github └── FUNDING.yml /.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/database/.gitignore: -------------------------------------------------------------------------------- 1 | .env.* -------------------------------------------------------------------------------- /packages/ui/src/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/pages/forgot-password.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/pages/settings/security.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/pages/settings/sessions.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/components/PagesVerifyPhone.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/pages/settings/billing/index.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/public/.assetsignore: -------------------------------------------------------------------------------- 1 | _worker.js 2 | _routes.json -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /packages/database/src/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./main"; 3 | -------------------------------------------------------------------------------- /apps/web/.env.example: -------------------------------------------------------------------------------- 1 | PUBLIC_API_URL=http://localhost:8080 2 | PUBLIC_SITE_URL=http://localhost:4321 -------------------------------------------------------------------------------- /packages/constants/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./appInfo"; 2 | export * from "./tailwindConfig"; 3 | -------------------------------------------------------------------------------- /apps/api/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | npm run deploy 8 | ``` 9 | -------------------------------------------------------------------------------- /apps/web/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "wrangler.json": "jsonc" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeoStack Template 2 | 3 | 4 | If you like the template feel free to donate at https://ko-fi.com/theneocorner 5 | -------------------------------------------------------------------------------- /packages/constants/src/appInfo.ts: -------------------------------------------------------------------------------- 1 | export const appInfo = { 2 | name: "NeoStack", 3 | email: "no-reply@neoprint3d.dev", 4 | }; 5 | -------------------------------------------------------------------------------- /packages/database/migrations/0002_dusty_mephistopheles.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "transcriptions" RENAME COLUMN "srt_path" TO "subtitle_path"; -------------------------------------------------------------------------------- /packages/typescript-config/README.md: -------------------------------------------------------------------------------- 1 | # `@workspace/typescript-config` 2 | 3 | Shared typescript configuration for the workspace. 4 | -------------------------------------------------------------------------------- /apps/web/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /apps/web/src/pages/dashboard/record.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import LayoutDashboard from "@/layouts/LayoutDashboard.astro"; 3 | --- 4 | 5 | test 6 | -------------------------------------------------------------------------------- /apps/web/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesHome } from "@/components/PagesHome"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/database/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "drizzle-orm"; 2 | export * from "./schema"; 3 | export * as schema from "./schema"; 4 | export { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js"; 5 | -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/database/migrations/0001_watery_satana.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "transcript_chunks" ADD COLUMN "start_time" integer NOT NULL;--> statement-breakpoint 2 | ALTER TABLE "transcript_chunks" ADD COLUMN "end_time" integer NOT NULL; -------------------------------------------------------------------------------- /apps/web/src/pages/verify-email.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesVerifyEmail } from "@/components/PagesVerifyEmail"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "declaration": true, 6 | "declarationMap": true 7 | }, 8 | "include": ["src/client.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/typescript-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neostack/typescript-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "PROPRIETARY", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/typescript-config/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/src/pages/reset-password.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesResetPassword } from "@/components/PagesResetPassword"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/api/src/lib/database.ts: -------------------------------------------------------------------------------- 1 | import { AppEnv } from "@/types/AppEnv"; 2 | import { drizzle, PostgresJsDatabase } from "@neostack/database"; 3 | 4 | export const db = (env: AppEnv): PostgresJsDatabase => 5 | drizzle(env.HYPERDRIVE.connectionString); 6 | -------------------------------------------------------------------------------- /apps/web/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/database/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | 3 | export default defineConfig({ 4 | schema: "./src/schema", 5 | dialect: "postgresql", 6 | out: "./migrations", 7 | dbCredentials: { 8 | url: process.env.DATABASE_URL!, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/web/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { AppHeader } from "@/components/AppHeader"; 3 | import LayoutBase from "./LayoutBase.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/constants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": false, 4 | "skipLibCheck": true, 5 | "strict": true, 6 | "noEmit": true, 7 | "strictNullChecks": true 8 | }, 9 | "include": ["src"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@neostack/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@neostack/ui/*": ["./src/*"] 7 | } 8 | }, 9 | "include": ["."], 10 | "exclude": ["node_modules", "dist"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/pages/login.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesLogin } from "@/components/PagesLogin"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | 5 | if (Astro.locals.auth) { 6 | return Astro.redirect("/dashboard"); 7 | } 8 | --- 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/web/src/pages/register.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesRegister } from "@/components/PagesRegister"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | 5 | if (Astro.locals.auth) { 6 | return Astro.redirect("/dashboard", 302); 7 | } 8 | --- 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/web/src/pages/settings/user.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // pages/settings/user.astro 3 | import { PagesSettingsUser } from "@/components/PagesSettingsUser"; 4 | 5 | if (!Astro.locals.auth) { 6 | return Astro.redirect("/login", 302); 7 | } 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/typescript-config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | 5 | "compilerOptions": { 6 | "target": "ESNext", 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler", 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "lib": ["ESNext"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": ["ESNext"], 9 | "baseUrl": ".", 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | } 13 | }, 14 | "include": ["src/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/ui/src/components/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | function AspectRatio({ 6 | ...props 7 | }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | export { AspectRatio } 12 | -------------------------------------------------------------------------------- /packages/constants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neostack/constants", 3 | "version": "0.0.0", 4 | "exports": { 5 | ".": { 6 | "import": "./src/index.ts" 7 | } 8 | }, 9 | "scripts": {}, 10 | "packageManager": "pnpm@10.8.1", 11 | "dependencies": { 12 | "colorizr": "^3.0.7", 13 | "@neostack/typescript-config": "workspace:*" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/pages/dashboard/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesDashboard } from "@/components/PagesDashboard"; 3 | import LayoutDashboard from "@/layouts/LayoutDashboard.astro"; 4 | 5 | if (!Astro.locals.auth) { 6 | return Astro.redirect("/login", 302); 7 | } 8 | --- 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/ui/src/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@neostack/ui/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /apps/web/src/pages/dashboard/transcripts/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import LayoutDashboard from "@/layouts/LayoutDashboard.astro"; 3 | import { PagesDashboardTranscripts } from "@/components/PagesDashboardTranscripts"; 4 | if (!Astro.locals.auth) { 5 | return Astro.redirect("/login", 302); 6 | } 7 | --- 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/web/src/lib/apiClient.ts: -------------------------------------------------------------------------------- 1 | import { api } from "@neostack/api/client"; 2 | 3 | export const apiClient = api(import.meta.env.PUBLIC_API_URL, { 4 | init: { 5 | credentials: "include", 6 | }, 7 | }); 8 | 9 | export type ExtractedApiData< 10 | T extends (...args: any[]) => Promise<{ json: () => Promise }>, 11 | K extends string, 12 | > = Awaited>["json"]>>[K]; 13 | -------------------------------------------------------------------------------- /apps/api/.dev.vars.example: -------------------------------------------------------------------------------- 1 | BETTER_AUTH_SECRET="BETTER_AUTH_SECRET" 2 | BETTER_AUTH_URL="http://localhost:8080" 3 | 4 | TRUSTED_ORIGINS="http://localhost:4321" 5 | DATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/postgres" 6 | 7 | GOOGLE_CLIENT_ID="GOOGLE_CLIENT_ID" 8 | GOOGLE_CLIENT_SECRET="GOOGLE_CLIENT_SECRET" 9 | 10 | STRIPE_SECRET_KEY="STRIPE_SECRET_KEY" 11 | STRIPE_WEBHOOK_SECRET="STRIPE_WEBHOOK_SECRET" -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": ["ESNext"], 9 | "types": ["@cloudflare/workers-types/2023-07-01"], 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | }, 13 | "jsx": "react-jsx", 14 | "jsxImportSource": "react" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/src/pages/dashboard/file-upload.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import LayoutDashboard from "@/layouts/LayoutDashboard.astro"; 3 | import { PagesDashboardAudioFileUpload } from "@/components/PagesDashboardFileUpload"; 4 | if (!Astro.locals.auth) { 5 | return Astro.redirect("/login", 302); 6 | } 7 | --- 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.* 19 | 20 | !.env.example 21 | 22 | # macOS-specific files 23 | .DS_Store 24 | 25 | # jetbrains setting folder 26 | .idea/ 27 | 28 | # wrangler files 29 | .wrangler 30 | .dev.vars* 31 | -------------------------------------------------------------------------------- /apps/web/src/env.d.ts: -------------------------------------------------------------------------------- 1 | type Runtime = import("@astrojs/cloudflare").Runtime; 2 | 3 | interface Auth { 4 | user: import("better-auth").User; 5 | session: import("better-auth").Session; 6 | } 7 | declare namespace App { 8 | interface Locals extends Runtime { 9 | auth: Auth | null; 10 | } 11 | } 12 | 13 | interface ImportMetaEnv { 14 | PUBLIC_API_URL: string; 15 | PUBLIC_SITE_URL: string; 16 | } 17 | 18 | interface ImportMeta { 19 | readonly env: ImportMetaEnv; 20 | } 21 | -------------------------------------------------------------------------------- /packages/email/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": false, 4 | "jsx": "react-jsx", 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "strictNullChecks": true, 9 | "paths": { 10 | "@/*": [ 11 | "./src/*" 12 | ], 13 | "@neostack/constants": [ 14 | "../../packages/constants/src/index.ts" 15 | ] 16 | }, 17 | }, 18 | "include": ["**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules", ".react-email"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/email/readme.md: -------------------------------------------------------------------------------- 1 | # React Email Starter 2 | 3 | A live preview right in your browser so you don't need to keep sending real emails during development. 4 | 5 | ## Getting Started 6 | 7 | First, install the dependencies: 8 | 9 | ```sh 10 | npm install 11 | # or 12 | yarn 13 | ``` 14 | 15 | Then, run the development server: 16 | 17 | ```sh 18 | npm run dev 19 | # or 20 | yarn dev 21 | ``` 22 | 23 | Open [localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | ## License 26 | 27 | MIT License 28 | -------------------------------------------------------------------------------- /.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 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | 34 | # Misc 35 | .DS_Store 36 | *.pem 37 | -------------------------------------------------------------------------------- /apps/web/src/utils/deriveUserData.ts: -------------------------------------------------------------------------------- 1 | export function deriveUserData(initialAuth: Auth | null) { 2 | if (!initialAuth?.user) return null; 3 | 4 | const name = 5 | initialAuth.user.name || initialAuth.user.email?.split("@")[0] || "User"; 6 | 7 | return { 8 | name, 9 | email: initialAuth.user.email || "no-email@example.com", 10 | avatar: initialAuth.user.image, 11 | initials: name 12 | .split(" ") 13 | .map((n) => n[0]) 14 | .join("") 15 | .slice(0, 2) 16 | .toUpperCase(), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /apps/api/.gitignore: -------------------------------------------------------------------------------- 1 | # prod 2 | dist/ 3 | 4 | # dev 5 | .yarn/ 6 | !.yarn/releases 7 | .vscode/* 8 | !.vscode/launch.json 9 | !.vscode/*.code-snippets 10 | .idea/workspace.xml 11 | .idea/usage.statistics.xml 12 | .idea/shelf 13 | 14 | # deps 15 | node_modules/ 16 | .wrangler 17 | 18 | # env 19 | .env 20 | .env.production 21 | .dev.vars 22 | 23 | # logs 24 | logs/ 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | pnpm-debug.log* 30 | lerna-debug.log* 31 | 32 | # misc 33 | .DS_Store 34 | 35 | .dev.vars.* 36 | !.dev.vars.example -------------------------------------------------------------------------------- /apps/web/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authClient } from "@/lib/authClient"; 2 | import { defineMiddleware } from "astro:middleware"; 3 | 4 | export const onRequest = defineMiddleware(async (context, next) => { 5 | const { data, error } = await authClient.getSession({ 6 | fetchOptions: { 7 | headers: context.request.headers, 8 | }, 9 | }); 10 | console.log("fetching user"); 11 | if (data && !error) { 12 | context.locals.auth = data; 13 | } else { 14 | context.locals.auth = null; 15 | } 16 | 17 | return next(); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/web/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": "", 8 | "css": "../../packages/ui/src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@/components", 15 | "hooks": "@/hooks", 16 | "lib": "@/lib", 17 | "utils": "@neostack/ui/lib/utils", 18 | "ui": "@neostack/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/ui/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": "", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@neostack/ui/components", 15 | "utils": "@neostack/ui/lib/utils", 16 | "hooks": "@neostack/ui/hooks", 17 | "lib": "@neostack/ui/lib", 18 | "ui": "@neostack/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": [ 4 | ".astro/types.d.ts", 5 | "**/*" 6 | ], 7 | "exclude": [ 8 | "dist" 9 | ], 10 | "compilerOptions": { 11 | "strict": true, 12 | "types": [ 13 | "@cloudflare/workers-types/2023-07-01" 14 | ], 15 | "baseUrl": "./", 16 | "paths": { 17 | "@/*": [ 18 | "./src/*" 19 | ], 20 | "@neostack/ui/*": [ 21 | "../../packages/ui/src/*" 22 | ] 23 | }, 24 | "jsx": "react-jsx", 25 | "jsxImportSource": "react" 26 | } 27 | } -------------------------------------------------------------------------------- /packages/database/db-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --name postgres-local -e POSTGRES_PASSWORD=mysecretpassword -d -p 5432:5432 --restart always -v postgres-data:/var/lib/postgresql/data supabase/postgres:15.8.1.024 4 | 5 | 6 | # pgadmin web interface make sure to include the actual database host and port 7 | docker run --name pgadmin-local -p 5050:80 -e PGADMIN_DEFAULT_EMAIL="user@domain.com" -e PGADMIN_DEFAULT_PASSWORD="admin" -v pgadmin-data:/var/lib/pgadmin --restart always -d dpage/pgadmin4:8.14 8 | # cli mode 9 | psql -U postgres 10 | CREATE EXTENSION vector; 11 | CREATE EXTENSION postgis; 12 | 13 | -------------------------------------------------------------------------------- /packages/database/src/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "incremental": false, 9 | "isolatedModules": true, 10 | "lib": ["es2022", "DOM", "DOM.Iterable"], 11 | "module": "NodeNext", 12 | "moduleDetection": "force", 13 | "moduleResolution": "NodeNext", 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "ES2022" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/database/migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "7", 8 | "when": 1744791396792, 9 | "tag": "0000_daffy_ego", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "7", 15 | "when": 1744833008439, 16 | "tag": "0001_watery_satana", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "7", 22 | "when": 1744842490014, 23 | "tag": "0002_dusty_mephistopheles", 24 | "breakpoints": true 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neostack", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "build": "turbo build", 7 | "dev": "turbo dev", 8 | "lint": "turbo lint", 9 | "format": "prettier --write \"**/*.{ts,tsx,md}\"" 10 | }, 11 | "devDependencies": { 12 | "@neostack/typescript-config": "workspace:*", 13 | "prettier": "^3.5.1", 14 | "turbo": "^2.4.2", 15 | "typescript": "5.7.3" 16 | }, 17 | "packageManager": "pnpm@10.8.1", 18 | "engines": { 19 | "node": ">=20" 20 | }, 21 | "pnpm": { 22 | "ignoredBuiltDependencies": [ 23 | "esbuild", 24 | "workerd" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/email/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neostack/email", 3 | "version": "1.1.0", 4 | "scripts": { 5 | "build": "email build", 6 | "dev": "email dev", 7 | "export": "email export" 8 | }, 9 | "exports": { 10 | "./*": "./emails/*.tsx" 11 | }, 12 | "workspaces": [ 13 | ".react-email" 14 | ], 15 | "dependencies": { 16 | "@neostack/constants": "workspace:*", 17 | "@react-email/components": "^0.0.36", 18 | "react-dom": "19.1.0", 19 | "react": "19.1.0" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "19.1.1", 23 | "@types/react-dom": "19.1.2", 24 | "react-email": "^4.0.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/src/layouts/LayoutDashboard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { LayoutDashboard as LayoutDashboardReact } from "@/components/LayoutDashboard"; 3 | import { Toaster } from "@neostack/ui/components/sonner"; 4 | import LayoutBase from "./LayoutBase.astro"; 5 | const { pageName, customPadding } = Astro.props; 6 | --- 7 | 8 | 9 | 17 |
18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /packages/ui/src/components/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner, ToasterProps } from "sonner" 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | 22 | ) 23 | } 24 | 25 | export { Toaster } 26 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState( 7 | undefined 8 | ); 9 | 10 | React.useEffect(() => { 11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 12 | const onChange = () => { 13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 14 | }; 15 | mql.addEventListener("change", onChange); 16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 17 | return () => mql.removeEventListener("change", onChange); 18 | }, []); 19 | 20 | return !!isMobile; 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/lib/authClient.ts: -------------------------------------------------------------------------------- 1 | import { createAuthClient } from "better-auth/react"; 2 | import { organizationClient, adminClient } from "better-auth/client/plugins"; 3 | import { toast } from "sonner"; 4 | import { navigate } from "astro:transitions/client"; 5 | export const authClient = createAuthClient({ 6 | baseURL: `${import.meta.env.PUBLIC_API_URL}/v1/auth`, 7 | plugins: [organizationClient(), adminClient()], 8 | }); 9 | 10 | export const handleSignOut = async () => { 11 | await authClient.signOut({ 12 | fetchOptions: { 13 | onSuccess: async () => { 14 | toast.success("Signed out successfully!"); 15 | await navigate("/login"); 16 | }, 17 | }, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/DashboardHeader.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarTrigger } from "@neostack/ui/components/sidebar"; 2 | import { NotificationBell } from "./NotificationBell"; 3 | 4 | interface DashboardHeaderProps { 5 | pageName: string; 6 | } 7 | export default function DashboardHeader({ pageName }: DashboardHeaderProps) { 8 | return ( 9 |
10 | 11 |

12 | {pageName} 13 |

14 |
15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/api/src/middleware/allowBrowser.ts: -------------------------------------------------------------------------------- 1 | import { AppContext } from "@/types/AppEnv"; 2 | import { Context, Next } from "hono"; 3 | import { env } from "hono/adapter"; 4 | import { cors } from "hono/cors"; 5 | 6 | export async function allowBrowser(c: Context, next: Next) { 7 | if (c.req.header("Upgrade") === "websocket") { 8 | return await next(); 9 | } 10 | const corsMiddleware = cors({ 11 | origin: c.env.TRUSTED_ORIGINS.split(","), 12 | allowHeaders: ["Content-Type", "Authorization", "Cookie"], 13 | allowMethods: ["POST", "GET", "PUT", "DELETE", "OPTIONS"], 14 | exposeHeaders: ["Content-Length"], 15 | maxAge: 600, 16 | credentials: true, 17 | }); 18 | return await corsMiddleware(c, next); 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui/src/components/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | 6 | import { cn } from "@neostack/ui/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /apps/web/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /apps/api/src/client.ts: -------------------------------------------------------------------------------- 1 | import { hc } from "hono/client"; 2 | import { app } from "."; 3 | import { createAuthClient } from "./lib/auth"; 4 | import { z } from "zod"; 5 | 6 | const client = hc(""); 7 | 8 | export type ApiClient = typeof client; 9 | 10 | export const api = (...args: Parameters): ApiClient => 11 | hc(...args); 12 | 13 | export type BetterAuthInstance = Awaited>; 14 | export { TranscriptionData } from "@/types/AppEnv"; 15 | export type Notification = z.infer; 16 | 17 | export const notificationSchema = z.object({ 18 | id: z.string(), 19 | type: z.enum(["queueStatus", "invitation"]), 20 | timestamp: z.number(), 21 | redirectPath: z.string().optional(), 22 | title: z.string(), 23 | content: z.string(), 24 | }); 25 | -------------------------------------------------------------------------------- /apps/web/src/components/PagesDashboard.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardHeader, 4 | CardTitle, 5 | CardDescription, 6 | } from "@neostack/ui/components/card"; 7 | 8 | export function PagesDashboard() { 9 | return ( 10 |
11 |

12 | Transcription Dashboard 13 |

14 |
15 | {/* Form Card */} 16 | 17 | 18 | Queue New Transcription 19 | 20 | Submit an audio URL to start processing. 21 | 22 | 23 | 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/src/layouts/LayoutBase.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { AppHeader } from "@/components/AppHeader"; 3 | import { ClientRouter } from "astro:transitions"; 4 | import { Toaster } from "@neostack/ui/components/sonner"; 5 | import "@neostack/ui/globals.css"; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Astro Basics 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | -------------------------------------------------------------------------------- /apps/web/src/pages/dashboard/transcripts/[id].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PagesDashboardTranscript } from "@/components/PagesDashboardTranscript"; 3 | import LayoutDashboard from "@/layouts/LayoutDashboard.astro"; 4 | import { drizzle, transcriptions, eq } from "@neostack/database"; 5 | 6 | // Extract parameters and environment variables 7 | const { id } = Astro.params; 8 | const { HYPERDRIVE, BUCKET } = Astro.locals.runtime.env; 9 | 10 | const db = drizzle(HYPERDRIVE.connectionString); 11 | 12 | const transcript = ( 13 | await db.select().from(transcriptions).where(eq(transcriptions.id, id!)) 14 | )[0]; 15 | 16 | if (!transcript) { 17 | throw new Response("Transcript Not Found", { status: 404 }); 18 | } 19 | --- 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/ui/src/components/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "@neostack/ui/lib/utils"; 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ); 26 | } 27 | 28 | export { Separator }; 29 | -------------------------------------------------------------------------------- /packages/ui/src/components/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@neostack/ui/lib/utils"; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |