Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform #62

Merged
merged 7 commits into from
Jan 16, 2025
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
104 changes: 52 additions & 52 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
name: 📦 Release Packages
# name: 📦 Release Packages

on:
push:
branches:
- main
paths-ignore:
- 'apps/**'
# on:
# push:
# branches:
# - main
# paths-ignore:
# - 'apps/**'

jobs:
test:
name: 🧪 Run tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: 📦 Install dependencies
run: bun install
- name: 🔍 Run type checking
run: bun run typecheck
- name: 🔬 Run linting
run: bun run lint
- name: 🧪 Run unit tests
run: bun run test
# jobs:
# test:
# name: 🧪 Run tests
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: oven-sh/setup-bun@v1
# with:
# bun-version: latest
# - name: 📦 Install dependencies
# run: bun install
# - name: 🔍 Run type checking
# run: bun run typecheck
# - name: 🔬 Run linting
# run: bun run lint
# - name: 🧪 Run unit tests
# run: bun run test

create-release:
name: 📦 Create Release
needs: [test]
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: 📦 Install dependencies
run: bun install
- name: 🏗️ Build packages
run: bun turbo build --filter=languine --filter=@languine/react-email
- name: 🔖 Create and publish versions
uses: changesets/action@master
with:
version: bun run changeset version
commit: "chore: update versions"
title: "chore: update versions"
publish: bun run changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# create-release:
# name: 📦 Create Release
# needs: [test]
# runs-on: ubuntu-latest
# permissions:
# contents: write
# pull-requests: write
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
# - uses: oven-sh/setup-bun@v1
# with:
# bun-version: latest
# - name: 📦 Install dependencies
# run: bun install
# - name: 🏗️ Build packages
# run: bun turbo build --filter=languine --filter=@languine/react-email
# - name: 🔖 Create and publish versions
# uses: changesets/action@master
# with:
# version: bun run changeset version
# commit: "chore: update versions"
# title: "chore: update versions"
# publish: bun run changeset publish
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ dist/

# deps
node_modules/
.wrangler

# env
.env
Expand Down
20 changes: 10 additions & 10 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@
"@react-email/components": "0.0.32",
"@react-email/font": "^0.0.9",
"@tanstack/react-query": "^5.64.1",
"@trpc/client": "^11.0.0-rc.688",
"@trpc/react-query": "^11.0.0-rc.688",
"@trpc/server": "^11.0.0-rc.688",
"@trpc/client": "11.0.0-rc.700",
"@trpc/react-query": "11.0.0-rc.700",
"@trpc/server": "11.0.0-rc.700",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.3",
"@vercel/functions": "^1.5.2",
"ai": "^4.0.34",
"better-auth": "^1.1.11",
"ai": "^4.0.38",
"better-auth": "^1.1.13",
"class-variance-authority": "^0.7.1",
"client-only": "^0.0.1",
"clsx": "^2.1.1",
Expand All @@ -53,29 +53,29 @@
"drizzle-orm": "^0.38.3",
"input-otp": "^1.4.2",
"lucide-react": "^0.471.1",
"motion": "^11.17.1",
"motion": "^11.18.0",
"next": "15.1.4",
"next-international": "^1.3.1",
"next-themes": "^0.4.4",
"nuqs": "^2.3.0",
"nuqs": "^2.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-icons": "^5.4.0",
"recharts": "^2.15.0",
"resend": "^4.0.1",
"resend": "^4.1.1",
"server-only": "^0.0.1",
"sonner": "^1.7.1",
"sonner": "^1.7.2",
"superjson": "^2.2.2",
"tailwind-merge": "^2.6.0",
"zod": "^3.24.1"
},
"devDependencies": {
"languine": "link:languine",
"@types/node": "^22",
"@types/react": "^19",
"react-email": "3.0.6",
"@types/react-dom": "^19",
"languine": "^1.0.2",
"postcss": "^8",
"tailwindcss": "^3.4.17",
"typescript": "^5"
Expand Down
22 changes: 22 additions & 0 deletions apps/web/src/app/[locale]/(dashboard)/cli/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getSession } from "@/lib/session";
import { getI18n } from "@/locales/server";
import { redirect } from "next/navigation";

export default async function Page() {
const t = await getI18n();
const session = await getSession();

if (!session.data) {
redirect("/login");
}

return (
<div className="w-full h-screen flex flex-col items-center justify-center">
<h1 className="text-xl font-medium mb-4">{t("cli.success.title")}</h1>
<p className="text-center mb-2 text-sm text-secondary">
{t("cli.success.description")} <span>{session.data.user.email}</span>
</p>
<p className="text-sm text-secondary">{t("cli.success.description_2")}</p>
</div>
);
}
2 changes: 1 addition & 1 deletion apps/web/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default async function RootLayout({
params,
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
params: Promise<{ locale: string }>;
}>) {
const { locale } = await params;

Expand Down
52 changes: 52 additions & 0 deletions apps/web/src/app/api/auth/cli/[token]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { CLI_TOKEN_NAME, saveCLISession } from "@/lib/auth/cli";
import { getSession } from "@/lib/session";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { NextResponse } from "next/server";

const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, "60s"),
});

export async function GET(
request: Request,
{ params }: { params: Promise<{ token: string }> },
) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);

if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}

const { token } = await params;

const session = await getSession();

if (!session?.data) {
const response = NextResponse.redirect(new URL("/login", request.url), {
status: 302,
});

if (token) {
response.cookies.set(CLI_TOKEN_NAME, token, {
maxAge: 5 * 60,
});
}

return response;
}

if (session?.data?.session) {
await saveCLISession(session.data.session, token);
}

const response = NextResponse.redirect(new URL("/cli/success", request.url), {
status: 302,
});

response.cookies.delete(CLI_TOKEN_NAME);

return response;
}
39 changes: 39 additions & 0 deletions apps/web/src/app/api/auth/cli/[token]/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { db } from "@/db";
import { users } from "@/db/schema";
import { getCLISession } from "@/lib/auth/cli";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";

export async function GET(
request: Request,
{ params }: { params: Promise<{ token: string }> },
) {
const { token } = await params;

const cliSession = await getCLISession(token);

if (!cliSession) {
return NextResponse.json(
{
success: false,
},
{ status: 404 },
);
}

const user = await db
.select({
id: users.id,
name: users.name,
email: users.email,
apiKey: users.apiKey,
})
.from(users)
.where(eq(users.id, cliSession.userId))
.get();

return NextResponse.json({
success: true,
user,
});
}
4 changes: 2 additions & 2 deletions apps/web/src/components/onboarding-steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export function OnboardingSteps() {
{t("onboarding.steps.1.description")}
</p>
<CopyInput
value={`npx languine@latest --p=${project}`}
value={`npx languine@latest init --p=${project}`}
onCopy={() => setStep(2)}
className="border-dashed text-xs"
className="border-dashed"
/>
</CardContent>
</Card>
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/lib/auth/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { kv } from "@/lib/kv";
import type { Session } from "better-auth";
import { cookies } from "next/headers";

export const CLI_TOKEN_NAME = "cli-token";

export async function saveCLISession(session: Session, token: string) {
await kv.set(`${CLI_TOKEN_NAME}:${token}`, session, {
ex: 5 * 60,
});
}

export async function getCLISession(token: string) {
const data = await kv.get<Session>(`${CLI_TOKEN_NAME}:${token}`);

return data;
}
57 changes: 57 additions & 0 deletions apps/web/src/lib/auth/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
createDefaultOrganization,
getDefaultOrganization,
} from "@/db/queries/organization";
import WelcomeEmail from "@/emails/templates/welcome";
import { resend } from "@/lib/resend";
import { waitUntil } from "@vercel/functions";
import type { Session, User } from "better-auth";
import { cookies } from "next/headers";
import { CLI_TOKEN_NAME, saveCLISession } from "./cli";

export const databaseHooks = {
user: {
create: {
after: async (user: User) => {
await createDefaultOrganization(user);

// Send welcome email to new user
try {
waitUntil(
resend.emails.send({
from: "Languine <[email protected]>",
to: user.email,
subject: "Welcome to Languine",
react: WelcomeEmail({ name: user.name }),
}),
);
} catch (error) {
console.error("Error sending welcome email", error);
}
},
},
},
session: {
create: {
before: async (session: Session) => {
const org = await getDefaultOrganization(session.userId);

const cookieStore = await cookies();
const token = cookieStore.get(CLI_TOKEN_NAME);

if (token?.value) {
await saveCLISession(session, token.value);

cookieStore.delete(CLI_TOKEN_NAME);
}

return {
data: {
...session,
activeOrganizationId: org?.organizations?.id,
},
};
},
},
},
};
Loading