Skip to content

Commit

Permalink
Structure
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Dec 30, 2024
1 parent d05ed5c commit d0a1b84
Show file tree
Hide file tree
Showing 20 changed files with 1,271 additions and 14 deletions.
Empty file added .github/workflows/deploy.yml
Empty file.
6 changes: 4 additions & 2 deletions .github/workflows/main.yml → .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
name: 🚀 Release Production
name: 📦 Release Packages

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

jobs:
test:
Expand Down Expand Up @@ -41,7 +43,7 @@ jobs:
run: bun install
- name: 🏗️ Build packages
run: bun turbo build --filter=languine --filter=@languine/react-email
- name: 🚀 Create and publish versions
- name: 🔖 Create and publish versions
uses: changesets/action@master
with:
version: bun run changeset version
Expand Down
4 changes: 3 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"name": "@languine/api",
"scripts": {
"dev": "wrangler dev --port 3002",
"deploy": "wrangler deploy --minify"
"deploy": "wrangler deploy --minify",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hono/oauth-providers": "^0.6.2",
"@hono/zod-validator": "^0.4.2",
"@scalar/hono-api-reference": "^0.5.165",
"hono": "^4.6.15",
Expand Down
6 changes: 2 additions & 4 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { apiReference } from "@scalar/hono-api-reference";
import { Hono } from "hono";
import { openAPISpecs } from "hono-openapi";
import fineTuneRouter from "./routes/fine-tune";
import telemetryRouter from "./routes/telemetry";
import router from "./routes";

const app = new Hono();

app.route("/telemetry", telemetryRouter);
app.route("/fine-tune", fineTuneRouter);
app.route("/", router);

app.get(
"/openapi",
Expand Down
234 changes: 234 additions & 0 deletions apps/api/src/routes/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { githubAuth } from "@hono/oauth-providers/github";
import { googleAuth } from "@hono/oauth-providers/google";
import { Hono } from "hono";
import { describeRoute } from "hono-openapi";
import { resolver } from "hono-openapi/zod";
import { authResponseSchema } from "./schema";

type Bindings = {
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
GOOGLE_CLIENT_ID: string;
GOOGLE_CLIENT_SECRET: string;
AUTH_REDIRECT_URI: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.use("/github", (c, next) => {
githubAuth({
client_id: c.env.GITHUB_CLIENT_ID,
client_secret: c.env.GITHUB_CLIENT_SECRET,
});

return next();
});

app.get(
"/github",
describeRoute({
description: "Handle GitHub OAuth authentication",
responses: {
200: {
description: "Successfully authenticated with GitHub",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
401: {
description: "Authentication failed",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
},
}),
async (c) => {
const token = c.get("token");
const profile = c.get("user-github");

if (!token || !profile) {
return c.json({ error: "Failed to authenticate with GitHub" }, 401);
}

return c.json({
data: {
token,
user: {
email: profile.email,
name: profile.name,
provider: "github",
},
},
});
},
);

app.use("/google", (c, next) => {
googleAuth({
client_id: c.env.GOOGLE_CLIENT_ID,
client_secret: c.env.GOOGLE_CLIENT_SECRET,
scope: ["openid", "email", "profile"],
});

return next();
});

app.get(
"/google",
describeRoute({
description: "Handle Google OAuth authentication",
responses: {
200: {
description: "Successfully authenticated with Google",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
401: {
description: "Authentication failed",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
},
}),
async (c) => {
const token = c.get("token");
const profile = c.get("user-google");

if (!token || !profile) {
return c.json({ error: "Failed to authenticate with Google" }, 401);
}

return c.json({
data: {
token,
user: {
email: profile.email,
name: profile.name,
provider: "google",
},
},
});
},
);

app.post(
"/token",
describeRoute({
description: "Exchange OAuth token for user info",
responses: {
200: {
description: "Successfully exchanged token",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
401: {
description: "Invalid token",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
},
}),
async (c) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");

if (!token) {
return c.json({ error: "No token provided" }, 401);
}

try {
// TODO: Implement token verification and user info retrieval
// This would involve:
// 1. Verifying the token's validity
// 2. Fetching associated user information
// 3. Returning the user data in the same format as the OAuth endpoints

return c.json({
data: {
token,
user: {
// User data would be populated from token verification
email: "",
name: "",
provider: "github", // or "google" depending on token
},
},
});
} catch (error) {
return c.json({ error: "Invalid token" }, 401);
}
},
);

app.post(
"/revalidate",
describeRoute({
description: "Revalidate an existing auth token",
responses: {
200: {
description: "Successfully revalidated token",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
401: {
description: "Invalid or expired token",
content: {
"application/json": {
schema: resolver(authResponseSchema),
},
},
},
},
}),
async (c) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");

if (!token) {
return c.json({ error: "No token provided" }, 401);
}

try {
// TODO: Implement token revalidation logic
// This would involve:
// 1. Verifying the token hasn't expired
// 2. Checking if the token is still valid
// 3. Optionally refreshing the token if needed
// 4. Returning updated token and user info

return c.json({
data: {
token,
user: {
// User data would be populated from token verification
email: "",
name: "",
provider: "github", // or "google" depending on token
},
},
});
} catch (error) {
return c.json({ error: "Invalid or expired token" }, 401);
}
},
);

export default app;
36 changes: 36 additions & 0 deletions apps/api/src/routes/auth/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import z from "zod";
import "zod-openapi/extend";

export const authResponseSchema = z
.object({
data: z
.object({
token: z.string().openapi({
example: "jwt_token",
description: "JWT authentication token",
}),
user: z
.object({
email: z.string().email().openapi({
example: "[email protected]",
description: "User's email address",
}),
name: z.string().openapi({
example: "John Doe",
description: "User's full name",
}),
provider: z.enum(["github", "google"]).openapi({
example: "github",
description: "OAuth provider used for authentication",
}),
})
.openapi({ description: "User profile information" }),
})
.optional()
.openapi({ description: "Response data" }),
error: z.string().optional().openapi({
example: "Authentication failed",
description: "Error message if request failed",
}),
})
.openapi({ ref: "AuthResponse" });
29 changes: 29 additions & 0 deletions apps/api/src/routes/feedback/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Hono } from "hono";
import { describeRoute } from "hono-openapi";
import { resolver, validator as zValidator } from "hono-openapi/zod";
import { bodySchema, responseSchema } from "./schema";

const app = new Hono();

app.post(
"/",
describeRoute({
description: "Submit feedback",
responses: {
200: {
description: "Successful feedback submission",
content: {
"application/json": {
schema: resolver(responseSchema),
},
},
},
},
}),
zValidator("json", bodySchema),
(c) => {
return c.json({ data: "Feedback received!" });
},
);

export default app;
31 changes: 31 additions & 0 deletions apps/api/src/routes/feedback/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import z from "zod";
import "zod-openapi/extend";

export const bodySchema = z
.object({
source: z
.string()
.openapi({ example: "en", description: "Source language code" }),
target: z
.string()
.openapi({ example: "es", description: "Target language code" }),
input: z
.string()
.openapi({ example: "Hello world", description: "Original text" }),
translation: z
.string()
.openapi({ example: "Hola mundo", description: "Machine translation" }),
suggestion: z
.string()
.optional()
.openapi({ example: "¡Hola mundo!", description: "User suggestion" }),
comment: z
.string()
.optional()
.openapi({ example: "Too formal", description: "Additional comments" }),
})
.openapi({ ref: "Body" });

export const responseSchema = z
.string()
.openapi({ example: "Feedback received!" });
Loading

0 comments on commit d0a1b84

Please sign in to comment.