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

Bots #27

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft

Bots #27

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
Binary file modified bun.lockb
Binary file not shown.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "NODE_ENV=development bun --hot src/app.ts",
"start": "bun src/app.ts",
"e2e-app-tests": "bun test app-crud --env-file=.env.test",
"e2e-app-tests": "bun test app-crud --env-file=.env.test --only",
"e2e-env-tests": "bun test environment-crud --env-file=.env.test",
"e2e-entity-tests": "bun test entity --env-file=.env.test",
"e2e-tests-all": "bun test --env-file=.env.test"
Expand All @@ -22,6 +22,9 @@
},
"dependencies": {
"@anthropic-ai/sdk": "^0.20.6",
"@clerk/backend": "^1.2.1",
"@clerk/clerk-sdk-node": "^5.0.7",
"@hono/clerk-auth": "^2.0.0",
"@langchain/openai": "^0.0.28",
"assert": "^2.1.0",
"axios": "^1.6.8",
Expand Down
26 changes: 22 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
import { Hono } from "hono";
import { logger } from "hono/logger";
import appsRoute from "./routes/applications";
import githubAuth from "./routes/auth/github";
import uiAuthRoute from "./routes/examples/auth";
import ragRoute from "./routes/rag";
import searchRoute from "./routes/search";
import mongoConnect from "./connections/mongodb.ts";
import webhooksRoute from "./routes/webhooks.ts";
import { cors } from "hono/cors";
import users from "./routes/users.ts";
import { clerkMiddleware } from "@hono/clerk-auth";
import contextMiddleware from "./middlewares/context.middleware.ts";
import authMiddleware from "./middlewares/auth.middleware.ts";

const app = new Hono();
if (Bun.env.NODE_ENV === "development") {
app.use(logger());
}

await mongoConnect();
app.use(contextMiddleware);
app.use(
cors({
origin: [
"https://7966-109-92-19-84.ngrok-free.app",
"http://localhost:5173",
],
}),
);

app.route("/webhooks", webhooksRoute);

app.use(clerkMiddleware());
app.route("/users", users);

app.use(authMiddleware);
// TODO auth middleware could be included here
app.route("/apps", appsRoute);
app.route("/search", searchRoute);
app.route("/knowledgebase", ragRoute);
app.route("/auth/github", githubAuth);
app.route("/examples/auth", uiAuthRoute);

export default app;
39 changes: 28 additions & 11 deletions src/middlewares/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { createFactory } from "hono/factory";
import { HTTPException } from "hono/http-exception";
import { verify as jwt_verify } from "hono/jwt";
import { bearerFromHeader } from "../utils/auth-utils";
import { getAuth } from "@hono/clerk-auth";
import { httpError } from "../utils/const.ts";
import { findUserByClerkId } from "../services/user.service.ts";

const factory = createFactory();

const middleware = factory.createMiddleware(async (c, next) => {
const authorizationHeader = c.req.header("Authorization");
const secret = Bun.env.JWT_SECRET;
if (!authorizationHeader || !secret) {
throw new HTTPException(401, { message: "Unauthorized request" });
const auth = getAuth(c);

if (!auth?.userId) {
throw new HTTPException(401, {
message: httpError.USER_NOT_AUTHENTICATED,
});
}
const bearerToken = bearerFromHeader(authorizationHeader);
try {
const user = await jwt_verify(bearerToken, secret);
const user = await findUserByClerkId({
id: auth.userId,
context: c.get("context"),
});

if (!user) {
throw new HTTPException(401, {
message: httpError.USER_NOT_AUTHENTICATED,
});
}

c.set("user", user);
await next();
} catch (err) {
console.log({ err });
throw new HTTPException(401, { message: "Unauthorized" });
} catch (e) {
if (e instanceof HTTPException) {
throw e;
} else {
throw new HTTPException(500, {
message: httpError.UNKNOWN,
});
}
}
});

Expand Down
39 changes: 34 additions & 5 deletions src/models/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,55 @@ import type { Application } from "./application.model";

const { Schema } = mongoose;

export const TelegramSchema = new Schema(
{
id: { type: Number, required: true, index: true, unique: true },
appName: { type: String, required: true },
envName: { type: String, required: true },
},
{ _id: false },
);

export const WhatsappSchema = new Schema(
{
id: { type: String, required: true, index: true, unique: true },
appName: { type: String, required: true },
envName: { type: String, required: true },
},
{ _id: false },
);

export const UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
index: true,
},
providers: { type: Array, default: [] },
applications: { type: Array, default: [] },
lastProvider: { type: String, default: "" },
clerkUserId: { type: String, required: true, unique: true },
lastUse: { type: Date, default: Date.now },
telegram: {
type: TelegramSchema,
required: false,
},
whatsapp: {
type: WhatsappSchema,
required: false,
},
});

export type User = {
email: string;
providers: string[];
applications: Application[];
lastProvider: string;
lastUse: Date;
}
clerkUserId: string;
telegram?: {
id: number;
appName: string;
envName: string;
};
};

const User = mongoose.model("User", UserSchema);
export default User;
13 changes: 10 additions & 3 deletions src/repositories/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type Application } from "../models/application.model.ts";
import { type Environment } from "../models/environment.model.ts";
import { type Entity } from "../models/entity.model.ts";
import { type User } from "../models/user.model.ts";
import type { EntityQueryMeta } from "../utils/types.ts";
import type { EntityQueryMeta, TelegramSettings } from "../utils/types.ts";
import type { EntityAggregateResult } from "../services/entity.service.ts";

export interface IApplicationRepository {
Expand Down Expand Up @@ -115,9 +115,16 @@ export interface IEntityRepository {

export interface IUserRepository {
createUser(props: {
provider: string;
clerkUserId: string;
email: string;
appName: string;
}): Promise<User>;
updateUser(props: { provider: string; email: string }): Promise<User | null>;
updateUserLastUse(props: { clerkUserId: string }): Promise<User | null>;
updateUserTelegramSettings(props: {
clerkUserId: string;
telegramSettings?: TelegramSettings;
}): Promise<User | null>;
findUserByEmail(email: string): Promise<User | null>;
findUserClerkId(id: string): Promise<User | null>;
findUserByTelegramId(id: number): Promise<User | null>;
}
63 changes: 50 additions & 13 deletions src/repositories/mongodb/user.repository.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,82 @@
import BaseRepository from "./base-repository.ts";
import { type User } from "../../models/user.model.ts";
import type { IUserRepository } from "../interfaces.ts";
import * as R from "ramda";
import type { TelegramSettings } from "../../utils/types.ts";

class UserRepository extends BaseRepository implements IUserRepository {
constructor() {
super();
}

public async createUser({
provider,
clerkUserId,
email,
appName,
}: {
provider: string;
clerkUserId: string;
email: string;
appName: string;
}): Promise<User> {
return this.userModel.create({
const result = await this.userModel.create({
email,
providers: [provider],
lastProvider: provider,
clerkUserId,
applications: [appName],
});

// convert null values to undefined
return R.defaultTo(undefined, result) as User;
}

public async updateUser({
provider,
email,
public async updateUserLastUse({
clerkUserId,
}: {
provider: string;
email: string;
clerkUserId: string;
}): Promise<User | null> {
return this.userModel.findOneAndUpdate(
{ email },
{ clerkUserId },
{
$addToSet: { providers: provider },
$set: { lastProvider: provider },
lastUse: Date.now(),
},
{ returnNewDocument: true },
);
}

public async updateUserTelegramSettings({
clerkUserId,
telegramSettings,
}: {
clerkUserId: string;
telegramSettings?: TelegramSettings;
}): Promise<User | null> {
const updateObj = telegramSettings?.telegramId
? {
"telegram.id": telegramSettings.telegramId,
"telegram.appName": telegramSettings.appName,
"telegram.envName": telegramSettings.envName,
}
: { telegram: undefined };

return this.userModel.findOneAndUpdate(
{ clerkUserId },
{
...updateObj,
},
{ returnNewDocument: true },
);
}

public async findUserByEmail(email: string): Promise<User | null> {
return this.userModel.findOne({ email });
}

public async findUserClerkId(id: string): Promise<User | null> {
return this.userModel.findOne({ clerkUserId: id });
}

public async findUserByTelegramId(id: number): Promise<User | null> {
return this.userModel.findOne({ "telegram.id": id });
}
}

export default UserRepository;
5 changes: 0 additions & 5 deletions src/routes/applications.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Hono } from "hono";
import { HTTPException } from "hono/http-exception";
import authMiddleware from "../middlewares/auth.middleware";
import {
createApplication,
deleteApplication,
Expand All @@ -12,7 +11,6 @@ import type { USER_TYPE } from "../utils/auth-utils";
import { APPNAME_MIN_LENGTH, APPNAME_REGEX, httpError } from "../utils/const";
import { ServiceError } from "../utils/service-errors";
import envsRoute from "./environments";
import contextMiddleware from "../middlewares/context.middleware.ts";
import type Context from "../middlewares/context.ts";

const app = new Hono<{
Expand All @@ -21,12 +19,9 @@ const app = new Hono<{
context: Context;
};
}>();
app.use(authMiddleware);
app.use(contextMiddleware);

app.get("/all", async (c) => {
const user = c.get("user");

const apps = await getUserApplications({
context: c.get("context"),
userEmail: user.email,
Expand Down
58 changes: 0 additions & 58 deletions src/routes/auth/github.ts

This file was deleted.

Loading