Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,78 +10,91 @@ jobs:
lint-and-typecheck:
name: Lint & Type Check
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
# Setup pnpm (uses version from package.json automatically)
- name: Setup pnpm
uses: pnpm/action-setup@v4

# Setup Node.js
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
node-version: 20
cache: pnpm

# Install dependencies
- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Run ESLint
run: npm run lint
run: pnpm lint
continue-on-error: true

- name: Type check frontend
run: npx tsc --noEmit
run: pnpm exec tsc --noEmit

- name: Type check Convex backend
run: npx tsc -p convex --noEmit
run: pnpm exec tsc -p convex --noEmit


build:
name: Build Application
runs-on: ubuntu-latest
needs: lint-and-typecheck

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
node-version: 20
cache: pnpm

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Set up Convex
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
CONVEX_DEPLOYMENT: ${{ secrets.CONVEX_DEPLOYMENT }}
run: |
npx convex dev --once --configure=new
run: pnpm exec convex dev --once --configure=new

- name: Build Next.js
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
CONVEX_DEPLOYMENT: ${{ secrets.CONVEX_DEPLOYMENT }}
run: npm run build
run: pnpm build


check-formatting:
name: Check Formatting
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
node-version: 20
cache: pnpm

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Check Prettier formatting
run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "convex/**/*.{ts,js}"
continue-on-error: true
run: pnpm exec prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "convex/**/*.{ts,js}"
continue-on-error: true
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ sendable-ai/
│ ├── auth/ # Authentication logic
│ ├── betterAuth/ # Better Auth configuration
│ ├── emails/ # Email templates
│ ├── todos/ # Example CRUD operations
│ ├── users/ # User management
│ └── schema.ts # Database schema
├── src/
Expand Down
24 changes: 12 additions & 12 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import type * as auth_mutations from "../auth/mutations.js";
import type * as auth_queries from "../auth/queries.js";
import type * as emails_components_BaseEmail from "../emails/components/BaseEmail.js";
import type * as emails_email from "../emails/email.js";
import type * as emails_magicLink from "../emails/magicLink.js";
import type * as emails_resetPassword from "../emails/resetPassword.js";
import type * as emails_verifyEmail from "../emails/verifyEmail.js";
import type * as emails_verifyOTP from "../emails/verifyOTP.js";
import type * as emails_templates_AlertEmail from "../emails/templates/AlertEmail.js";
import type * as emails_templates_BaseLayout from "../emails/templates/BaseLayout.js";
import type * as emails_templates_MagicLink from "../emails/templates/MagicLink.js";
import type * as emails_templates_ResetPassword from "../emails/templates/ResetPassword.js";
import type * as emails_templates_VerifyEmail from "../emails/templates/VerifyEmail.js";
import type * as emails_templates_VerifyOTP from "../emails/templates/VerifyOTP.js";
import type * as http from "../http.js";
import type * as todos_mutations from "../todos/mutations.js";
import type * as todos_queries from "../todos/queries.js";

import type {
ApiFromModules,
Expand All @@ -33,13 +33,13 @@ declare const fullApi: ApiFromModules<{
"auth/queries": typeof auth_queries;
"emails/components/BaseEmail": typeof emails_components_BaseEmail;
"emails/email": typeof emails_email;
"emails/magicLink": typeof emails_magicLink;
"emails/resetPassword": typeof emails_resetPassword;
"emails/verifyEmail": typeof emails_verifyEmail;
"emails/verifyOTP": typeof emails_verifyOTP;
"emails/templates/AlertEmail": typeof emails_templates_AlertEmail;
"emails/templates/BaseLayout": typeof emails_templates_BaseLayout;
"emails/templates/MagicLink": typeof emails_templates_MagicLink;
"emails/templates/ResetPassword": typeof emails_templates_ResetPassword;
"emails/templates/VerifyEmail": typeof emails_templates_VerifyEmail;
"emails/templates/VerifyOTP": typeof emails_templates_VerifyOTP;
http: typeof http;
"todos/mutations": typeof todos_mutations;
"todos/queries": typeof todos_queries;
}>;

/**
Expand Down
34 changes: 19 additions & 15 deletions convex/auth/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { components } from "../_generated/api";
import { components, api } from "../_generated/api";
import authSchema from "../betterAuth/schema";
import { createClient, GenericCtx } from "@convex-dev/better-auth";
import { betterAuth, type BetterAuthOptions } from "better-auth/minimal";
Expand All @@ -11,12 +11,6 @@ import {
magicLink,
emailOTP,
} from "better-auth/plugins";
import {
sendMagicLink,
sendOTPVerification,
sendEmailVerification,
sendResetPassword,
} from "../emails/email";
import { requireActionCtx } from "@convex-dev/better-auth/utils";
import { DataModel } from "../_generated/dataModel";
import authConfig from "../auth.config";
Expand All @@ -29,13 +23,18 @@ export const authComponent = createClient<DataModel, typeof authSchema>(
local: {
schema: authSchema,
},
verbose: false,
verbose: true,
},
);

export const createAuthOptions = (ctx: GenericCtx<DataModel>) => {
const finalSiteUrl = (process.env.SITE_URL || process.env.BETTER_AUTH_URL || "https://localhost:3000") as string;
if (!process.env.SITE_URL && !process.env.BETTER_AUTH_URL) {
console.warn("WARNING: SITE_URL or BETTER_AUTH_URL is not set in Convex environment variables. This will cause INVALID_ORIGIN errors.");
}
return {
baseURL: siteUrl,
baseURL: finalSiteUrl,
trustedOrigins: [finalSiteUrl],
database: authComponent.adapter(ctx),
account: {
accountLinking: {
Expand All @@ -45,19 +44,24 @@ export const createAuthOptions = (ctx: GenericCtx<DataModel>) => {
},
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmailVerification(requireActionCtx(ctx), {
// Ensure the URL uses the correct protocol and path
const verificationUrl = url.replace("http://localhost:3000", finalSiteUrl)
.replace("/api/auth/verify-email", "/verify-email");

await requireActionCtx(ctx).runAction(api.emails.email.sendEmailVerification, {
to: user.email,
url,
url: verificationUrl,
});
},
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendResetPassword: async ({ user, url }) => {
await sendResetPassword(requireActionCtx(ctx), {
const resetPasswordUrl = url.replace("http://localhost:3000", finalSiteUrl);
await requireActionCtx(ctx).runAction(api.emails.email.sendResetPassword, {
to: user.email,
url,
url: resetPasswordUrl,
});
},
},
Expand Down Expand Up @@ -89,15 +93,15 @@ export const createAuthOptions = (ctx: GenericCtx<DataModel>) => {
username(),
magicLink({
sendMagicLink: async ({ email, url }) => {
await sendMagicLink(requireActionCtx(ctx), {
await requireActionCtx(ctx).runAction(api.emails.email.sendMagicLink, {
to: email,
url,
});
},
}),
emailOTP({
async sendVerificationOTP({ email, otp }) {
await sendOTPVerification(requireActionCtx(ctx), {
await requireActionCtx(ctx).runAction(api.emails.email.sendOTPVerification, {
to: email,
code: otp,
});
Expand Down
Loading
Loading