refactor(api): remove all Stream Chat dependencies and operations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
54a13c3c30
commit
37a94ef2b3
17 changed files with 10 additions and 554 deletions
|
|
@ -33,7 +33,6 @@
|
|||
"multer": "^2.0.2",
|
||||
"nodemailer": "^7.0.4",
|
||||
"sharp": "^0.34.5",
|
||||
"stream-chat": "^9.8.0",
|
||||
"stripe": "^20.0.0",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ const baseSecrets: Secrets = {
|
|||
emailRefreshToken: "email-refresh-token",
|
||||
r2AccessKeyId: "r2-access-key-id",
|
||||
r2SecretAccessKey: "r2-secret-access-key",
|
||||
streamChatApiSecret: "stream-chat-api-secret",
|
||||
stripeSecretKey: "sk_live_secret_manager",
|
||||
stripeWebhookSecret: "whsec_live_secret_manager",
|
||||
streamChatApiSecretStaging: "stream-chat-api-secret-staging",
|
||||
stripeSecretKeyStaging: "sk_live_staging_secret_manager",
|
||||
stripeWebhookSecretStaging: "whsec_live_staging_secret_manager",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -427,26 +427,6 @@ describe("Middleware Tests", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("StreamChat Middleware", () => {
|
||||
it("should inject StreamChat client into context", async () => {
|
||||
const app = new Hono();
|
||||
app.use(middlewareManager.streamChat);
|
||||
app.get("/test", (c) => {
|
||||
const streamClient = // biome-ignore lint/suspicious/noExplicitAny: Needed for context access in tests
|
||||
(c as any).get("streamServerClient");
|
||||
return c.json({ hasStreamClient: !!streamClient });
|
||||
});
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
|
||||
const client = testClient(app) as any;
|
||||
const res = await client.test.$get();
|
||||
const data = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(data.hasStreamClient).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("R2 Middleware", () => {
|
||||
it("should inject S3 client into context", async () => {
|
||||
const app = new Hono();
|
||||
|
|
@ -531,18 +511,14 @@ describe("Middleware Tests", () => {
|
|||
it("should chain multiple middlewares correctly", async () => {
|
||||
const app = new Hono();
|
||||
app.use(middlewareManager.supabase);
|
||||
app.use(middlewareManager.streamChat);
|
||||
app.use(middlewareManager.stripe);
|
||||
app.get("/test", (c) => {
|
||||
const supabase = // biome-ignore lint/suspicious/noExplicitAny: Needed for context access in tests
|
||||
(c as any).get("supabase");
|
||||
const streamClient = // biome-ignore lint/suspicious/noExplicitAny: Needed for context access in tests
|
||||
(c as any).get("streamServerClient");
|
||||
const stripe = // biome-ignore lint/suspicious/noExplicitAny: Needed for context access in tests
|
||||
(c as any).get("stripe");
|
||||
return c.json({
|
||||
hasSupabase: !!supabase,
|
||||
hasStreamClient: !!streamClient,
|
||||
hasStripe: !!stripe,
|
||||
});
|
||||
});
|
||||
|
|
@ -554,7 +530,6 @@ describe("Middleware Tests", () => {
|
|||
|
||||
expect(res.status).toBe(200);
|
||||
expect(data.hasSupabase).toBe(true);
|
||||
expect(data.hasStreamClient).toBe(true);
|
||||
expect(data.hasStripe).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -562,7 +537,6 @@ describe("Middleware Tests", () => {
|
|||
const app = new Hono();
|
||||
app.use(middlewareManager.supabase);
|
||||
app.use(middlewareManager.auth); // This will fail
|
||||
app.use(middlewareManager.streamChat); // This should not execute
|
||||
app.get("/test", (c) => c.json({ success: true }));
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
|
||||
|
|
|
|||
|
|
@ -1,31 +1,11 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { testClient } from "hono/testing";
|
||||
import type { Channel, StreamChat } from "stream-chat";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createConfig } from "../../config.js";
|
||||
import { MiddlewareManager } from "../../middlewares/middleware.js";
|
||||
import { getMainRouter } from "../../routers/index.js";
|
||||
import { getTestUser } from "../helpers/dbSetup.js";
|
||||
|
||||
// Mock the stream-chat module
|
||||
vi.mock("stream-chat", () => {
|
||||
const mockChannel = {
|
||||
create: vi.fn().mockResolvedValue(undefined),
|
||||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const mockStreamChatInstance = {
|
||||
channel: vi.fn(() => mockChannel),
|
||||
upsertUser: vi.fn().mockResolvedValue({ users: {} }),
|
||||
};
|
||||
|
||||
return {
|
||||
StreamChat: {
|
||||
getInstance: vi.fn(() => mockStreamChatInstance),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock nodemailer
|
||||
const mockSendMail = vi.fn();
|
||||
vi.mock("nodemailer", () => ({
|
||||
|
|
@ -54,16 +34,7 @@ describe("Booking Endpoint", () => {
|
|||
const createdTablos: string[] = [];
|
||||
const createdUsers: string[] = [];
|
||||
|
||||
// Get references to the mocked functions for assertions
|
||||
let mockStreamChat: StreamChat;
|
||||
let mockChannel: Channel;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Get references to the mocked instances
|
||||
const { StreamChat } = await import("stream-chat");
|
||||
mockStreamChat = StreamChat.getInstance("test_api_key", "test_api_secret");
|
||||
mockChannel = mockStreamChat.channel("messaging", "test_channel_id");
|
||||
|
||||
// Get owner's short_user_id
|
||||
const { data: ownerProfile } = await supabase
|
||||
.from("profiles")
|
||||
|
|
@ -324,10 +295,6 @@ describe("Booking Endpoint", () => {
|
|||
createdUsers.push(userProfile.id);
|
||||
}
|
||||
|
||||
// Verify Stream Chat channel was created
|
||||
expect(mockChannel.create).toHaveBeenCalledTimes(1);
|
||||
expect(mockChannel.sendMessage).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Verify emails were sent (3 emails: welcome to new user, one to owner, one to booker)
|
||||
expect(mockSendMail).toHaveBeenCalledTimes(3);
|
||||
|
||||
|
|
@ -407,10 +374,6 @@ describe("Booking Endpoint", () => {
|
|||
createdTablos.push(data.tablo_id);
|
||||
createdBookings.push(data.tablo_id);
|
||||
|
||||
// Verify Stream Chat channel was created
|
||||
expect(mockChannel.create).toHaveBeenCalledTimes(1);
|
||||
expect(mockChannel.sendMessage).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Verify emails were sent (2 emails: one to owner, one to booker)
|
||||
expect(mockSendMail).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
|
@ -511,9 +474,6 @@ describe("Booking Endpoint", () => {
|
|||
expect(data2.tablo_id).toBe(firstTabloId);
|
||||
expect(data2.hasCreatedAccount).toBe(false);
|
||||
|
||||
// Stream Chat channel should still be created for the second booking
|
||||
expect(mockChannel.create).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Verify emails were sent for second booking (2 emails)
|
||||
expect(mockSendMail).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,35 +8,6 @@ import { getMainRouter } from "../../routers/index.js";
|
|||
import type { TestUserData } from "../helpers/dbSetup.js";
|
||||
import { getTestUser } from "../helpers/dbSetup.js";
|
||||
|
||||
// Mock Stream Chat operations
|
||||
const mockChannelCreate = vi.fn();
|
||||
const mockChannelUpdate = vi.fn();
|
||||
const mockChannelDelete = vi.fn();
|
||||
const mockChannelRemoveMembers = vi.fn();
|
||||
const mockChannelAddMembers = vi.fn();
|
||||
|
||||
// Mock the channel method to return our mocked channel
|
||||
const mockChannel = {
|
||||
create: mockChannelCreate,
|
||||
update: mockChannelUpdate,
|
||||
delete: mockChannelDelete,
|
||||
removeMembers: mockChannelRemoveMembers,
|
||||
addMembers: mockChannelAddMembers,
|
||||
};
|
||||
|
||||
// Mock the stream-chat module
|
||||
vi.mock("stream-chat", () => {
|
||||
const mockStreamChatInstance = {
|
||||
channel: vi.fn(() => mockChannel),
|
||||
};
|
||||
|
||||
return {
|
||||
StreamChat: {
|
||||
getInstance: vi.fn(() => mockStreamChatInstance),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock nodemailer for email sending
|
||||
const mockSendMail = vi.fn();
|
||||
vi.mock("nodemailer", () => ({
|
||||
|
|
@ -67,11 +38,6 @@ describe("Tablo Endpoint", () => {
|
|||
beforeEach(() => {
|
||||
// Reset all mocks before each test
|
||||
vi.clearAllMocks();
|
||||
mockChannelCreate.mockResolvedValue(undefined);
|
||||
mockChannelUpdate.mockResolvedValue(undefined);
|
||||
mockChannelDelete.mockResolvedValue(undefined);
|
||||
mockChannelRemoveMembers.mockResolvedValue(undefined);
|
||||
mockChannelAddMembers.mockResolvedValue(undefined);
|
||||
mockSendMail.mockResolvedValue({ messageId: "test-message-id" });
|
||||
});
|
||||
|
||||
|
|
@ -195,7 +161,7 @@ describe("Tablo Endpoint", () => {
|
|||
await supabaseAdmin.from("profiles").update({ plan: "standard" }).eq("id", ownerUser.userId);
|
||||
});
|
||||
|
||||
it("should allow owner to create a tablo and create a Stream Chat channel", async () => {
|
||||
it("should allow owner to create a tablo", async () => {
|
||||
const res = await createTabloRequest(ownerUser, client, {
|
||||
name: "New Owner Tablo",
|
||||
status: "todo",
|
||||
|
|
@ -205,11 +171,6 @@ describe("Tablo Endpoint", () => {
|
|||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("Tablo created successfully");
|
||||
|
||||
// Verify Stream Chat channel was created
|
||||
expect(mockChannelCreate).toHaveBeenCalledTimes(1);
|
||||
// Verify it was called (the channel is created with tablo data)
|
||||
expect(mockChannelCreate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should deny temp user from creating a tablo (regularUserCheck blocks temporary users)", async () => {
|
||||
|
|
@ -323,7 +284,6 @@ describe("Tablo Endpoint", () => {
|
|||
expect(res.status).toBe(403);
|
||||
const data = await res.json();
|
||||
expect(data.error).toBe("You have reached your tablo limit");
|
||||
expect(mockChannelCreate).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await supabaseAdmin
|
||||
.from("profiles")
|
||||
|
|
@ -392,17 +352,13 @@ describe("Tablo Endpoint", () => {
|
|||
});
|
||||
|
||||
describe("DELETE /tablos/delete - Delete Tablo", () => {
|
||||
it("should allow owner with admin access to delete tablo and delete Stream Chat channel", async () => {
|
||||
it("should allow owner with admin access to delete tablo", async () => {
|
||||
// Owner has admin access to their tablos
|
||||
const res = await deleteTabloRequest(ownerUser, client, "test_tablo_owner_private");
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("Tablo deleted successfully");
|
||||
|
||||
// Verify Stream Chat channel was deleted
|
||||
expect(mockChannelDelete).toHaveBeenCalledTimes(1);
|
||||
expect(mockChannelDelete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should deny temp user without admin access from deleting tablo", async () => {
|
||||
|
|
@ -558,7 +514,7 @@ describe("Tablo Endpoint", () => {
|
|||
return tabloId;
|
||||
};
|
||||
|
||||
it("should allow temp user to leave a shared tablo and remove from Stream Chat channel", async () => {
|
||||
it("should allow temp user to leave a shared tablo", async () => {
|
||||
const tabloId = await createSharedTabloForLeaveTest({
|
||||
ownerId: ownerUser.userId,
|
||||
memberId: temporaryUser.userId,
|
||||
|
|
@ -569,13 +525,9 @@ describe("Tablo Endpoint", () => {
|
|||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("Tablo left successfully");
|
||||
|
||||
// Verify Stream Chat channel removeMembers was called
|
||||
expect(mockChannelRemoveMembers).toHaveBeenCalledTimes(1);
|
||||
expect(mockChannelRemoveMembers).toHaveBeenCalledWith([temporaryUser.userId]);
|
||||
});
|
||||
|
||||
it("should allow owner to leave a tablo and remove from Stream Chat channel", async () => {
|
||||
it("should allow owner to leave a tablo", async () => {
|
||||
const tabloId = await createSharedTabloForLeaveTest({
|
||||
ownerId: temporaryUser.userId,
|
||||
memberId: ownerUser.userId,
|
||||
|
|
@ -587,10 +539,6 @@ describe("Tablo Endpoint", () => {
|
|||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("Tablo left successfully");
|
||||
|
||||
// Verify Stream Chat channel removeMembers was called
|
||||
expect(mockChannelRemoveMembers).toHaveBeenCalledTimes(1);
|
||||
expect(mockChannelRemoveMembers).toHaveBeenCalledWith([ownerUser.userId]);
|
||||
});
|
||||
|
||||
it("should deny unauthenticated leave request", async () => {
|
||||
|
|
|
|||
|
|
@ -6,27 +6,6 @@ import { createConfig } from "../../config.js";
|
|||
import { MiddlewareManager } from "../../middlewares/middleware.js";
|
||||
import { getMainRouter } from "../../routers/index.js";
|
||||
|
||||
// Mock Stream Chat operations
|
||||
const mockChannelUpdate = vi.fn();
|
||||
|
||||
// Mock the channel method to return our mocked channel
|
||||
const mockChannel = {
|
||||
update: mockChannelUpdate,
|
||||
};
|
||||
|
||||
// Mock the stream-chat module
|
||||
vi.mock("stream-chat", () => {
|
||||
const mockStreamChatInstance = {
|
||||
channel: vi.fn(() => mockChannel),
|
||||
};
|
||||
|
||||
return {
|
||||
StreamChat: {
|
||||
getInstance: vi.fn(() => mockStreamChatInstance),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Create S3 mock for calendar file operations
|
||||
const s3Mock = mockClient(S3Client);
|
||||
|
||||
|
|
@ -45,9 +24,6 @@ describe("Tasks Endpoint", () => {
|
|||
|
||||
// Mock PutObjectCommand for calendar file writes
|
||||
s3Mock.on(PutObjectCommand).resolves({});
|
||||
|
||||
// Mock Stream Chat channel update
|
||||
mockChannelUpdate.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("POST /tasks/sync-calendars - Sync Calendar Files", () => {
|
||||
|
|
@ -107,8 +83,8 @@ describe("Tasks Endpoint", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("POST /tasks/sync-tablo-names - Sync Tablo Names to Stream", () => {
|
||||
it("should call sync tablo names endpoint with basic auth and update Stream Chat channels (returns 200 if TASKS_SECRET properly configured)", async () => {
|
||||
describe("POST /tasks/sync-tablo-names - Sync Tablo Names", () => {
|
||||
it("should call sync tablo names endpoint with basic auth (returns 200 if TASKS_SECRET properly configured)", async () => {
|
||||
const res = await client.tasks["sync-tablo-names"].$post(
|
||||
{},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,25 +12,6 @@ import { MiddlewareManager } from "../../middlewares/middleware.js";
|
|||
import { getMainRouter } from "../../routers/index.js";
|
||||
import { getTestUser } from "../helpers/dbSetup.js";
|
||||
|
||||
// Mock Stream Chat operations
|
||||
const mockUpsertUser = vi.fn();
|
||||
const mockCreateToken = vi.fn();
|
||||
|
||||
// Create an instance object that holds the mocks (like the working pattern in tablo.test.ts)
|
||||
const mockStreamChatInstanceMethods = {
|
||||
upsertUser: mockUpsertUser,
|
||||
createToken: mockCreateToken,
|
||||
};
|
||||
|
||||
// Mock the stream-chat module
|
||||
vi.mock("stream-chat", () => {
|
||||
return {
|
||||
StreamChat: {
|
||||
getInstance: vi.fn(() => mockStreamChatInstanceMethods),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Create S3 mock for avatar operations
|
||||
const s3Mock = mockClient(S3Client);
|
||||
|
||||
|
|
@ -50,10 +31,6 @@ describe("User Endpoint", () => {
|
|||
vi.clearAllMocks();
|
||||
s3Mock.reset();
|
||||
|
||||
// Mock Stream Chat operations
|
||||
mockUpsertUser.mockResolvedValue({ users: { [ownerUser.userId]: {} } });
|
||||
mockCreateToken.mockReturnValue("mock-stream-token-123");
|
||||
|
||||
// Mock S3 operations
|
||||
s3Mock.on(PutObjectCommand).resolves({});
|
||||
s3Mock.on(ListObjectsV2Command).resolves({
|
||||
|
|
@ -63,7 +40,7 @@ describe("User Endpoint", () => {
|
|||
});
|
||||
|
||||
describe("GET /me - Get User Profile", () => {
|
||||
it("should return owner user profile with stream token", async () => {
|
||||
it("should return owner user profile", async () => {
|
||||
const res = await client.users.me.$get(
|
||||
{},
|
||||
{
|
||||
|
|
@ -78,14 +55,9 @@ describe("User Endpoint", () => {
|
|||
const data = await res.json();
|
||||
expect(data.id).toBe(ownerUser.userId);
|
||||
expect(data.email).toBe(ownerUser.email);
|
||||
expect(data.streamToken).toBe("mock-stream-token-123");
|
||||
|
||||
// Verify Stream Chat createToken was called
|
||||
expect(mockCreateToken).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateToken).toHaveBeenCalledWith(ownerUser.userId);
|
||||
});
|
||||
|
||||
it("should return temp user profile with stream token", async () => {
|
||||
it("should return temp user profile", async () => {
|
||||
const res = await client.users.me.$get(
|
||||
{},
|
||||
{
|
||||
|
|
@ -100,11 +72,6 @@ describe("User Endpoint", () => {
|
|||
const data = await res.json();
|
||||
expect(data.id).toBe(temporaryUser.userId);
|
||||
expect(data.email).toBe(temporaryUser.email);
|
||||
expect(data.streamToken).toBe("mock-stream-token-123");
|
||||
|
||||
// Verify Stream Chat createToken was called
|
||||
expect(mockCreateToken).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateToken).toHaveBeenCalledWith(temporaryUser.userId);
|
||||
});
|
||||
|
||||
it("should deny unauthenticated access", async () => {
|
||||
|
|
@ -114,63 +81,6 @@ describe("User Endpoint", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("POST /sign-up-to-stream - Sign Up User to Stream Chat", () => {
|
||||
it("should sign up owner user to stream chat", async () => {
|
||||
const res = await client.users["sign-up-to-stream"].$post(
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${ownerUser.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("User signed up to stream");
|
||||
|
||||
// Verify Stream Chat upsertUser was called
|
||||
expect(mockUpsertUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpsertUser).toHaveBeenCalledWith({
|
||||
id: ownerUser.userId,
|
||||
name: expect.any(String),
|
||||
language: "fr",
|
||||
});
|
||||
});
|
||||
|
||||
it("should sign up temp user to stream chat", async () => {
|
||||
const res = await client.users["sign-up-to-stream"].$post(
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${temporaryUser.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.message).toBe("User signed up to stream");
|
||||
|
||||
// Verify Stream Chat upsertUser was called
|
||||
expect(mockUpsertUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpsertUser).toHaveBeenCalledWith({
|
||||
id: temporaryUser.userId,
|
||||
name: expect.any(String),
|
||||
language: "fr",
|
||||
});
|
||||
});
|
||||
|
||||
it("should deny unauthenticated stream signup", async () => {
|
||||
const res = await client.users["sign-up-to-stream"].$post({});
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(mockUpsertUser).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /profile/avatar - Upload Avatar", () => {
|
||||
it("should upload avatar for owner user", async () => {
|
||||
const res = await client.users.profile.avatar.$post(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ export interface AppConfig {
|
|||
SUPABASE_SERVICE_ROLE_KEY: string;
|
||||
SUPABASE_CONNECTION_STRING: string;
|
||||
SUPABASE_CA_CERT: string;
|
||||
STREAM_CHAT_API_KEY: string;
|
||||
STREAM_CHAT_API_SECRET: string;
|
||||
STRIPE_SECRET_KEY: string;
|
||||
STRIPE_WEBHOOK_SECRET: string;
|
||||
STRIPE_SOLO_PRICE_ID: string;
|
||||
|
|
@ -59,8 +57,6 @@ export function createConfig(secrets?: Secrets): AppConfig {
|
|||
const isTestMode = NODE_ENV === "test";
|
||||
|
||||
const isStagingMode = NODE_ENV === "staging";
|
||||
const getStreamChatApiSecret = (isStagingMode: boolean) =>
|
||||
isStagingMode ? secrets!.streamChatApiSecretStaging : secrets!.streamChatApiSecret;
|
||||
const getStripeSecretKey = (isStagingMode: boolean) =>
|
||||
isStagingMode ? secrets!.stripeSecretKeyStaging : secrets!.stripeSecretKey;
|
||||
const getStripeWebhookSecret = (isStagingMode: boolean) =>
|
||||
|
|
@ -82,11 +78,6 @@ export function createConfig(secrets?: Secrets): AppConfig {
|
|||
SUPABASE_CA_CERT: isTestMode
|
||||
? validateEnvVar("SUPABASE_CA_CERT", process.env.SUPABASE_CA_CERT)
|
||||
: secrets!.supabaseCaCert,
|
||||
STREAM_CHAT_API_KEY: validateEnvVar("STREAM_CHAT_API_KEY", process.env.STREAM_CHAT_API_KEY),
|
||||
// Env dependent
|
||||
STREAM_CHAT_API_SECRET: isTestMode
|
||||
? validateEnvVar("STREAM_CHAT_API_SECRET", process.env.STREAM_CHAT_API_SECRET)
|
||||
: getStreamChatApiSecret(isStagingMode),
|
||||
STRIPE_SECRET_KEY: isTestMode
|
||||
? validateEnvVar("STRIPE_SECRET_KEY", process.env.STRIPE_SECRET_KEY)
|
||||
: getStripeSecretKeyFromEnv() || getStripeSecretKey(isStagingMode),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { SupabaseClient } from "@supabase/supabase-js";
|
|||
import type { EventAndTablo } from "@xtablo/shared-types";
|
||||
import type { Context, Next } from "hono";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import type { StreamChat } from "stream-chat";
|
||||
import { generatePassword } from "./token.js";
|
||||
|
||||
export const MAX_TABLO_LIMIT = 10;
|
||||
|
|
@ -290,7 +289,6 @@ export const verifyTabloLimitForUser = async (c: Context, next: Next) => {
|
|||
*/
|
||||
export const createInvitedUser = async (
|
||||
supabase: SupabaseClient,
|
||||
streamServerClient: StreamChat,
|
||||
transporter: Transporter,
|
||||
recipientEmail: string,
|
||||
senderEmail: string,
|
||||
|
|
@ -334,12 +332,6 @@ export const createInvitedUser = async (
|
|||
return { success: false, error: updateProfileError.message };
|
||||
}
|
||||
|
||||
await streamServerClient.upsertUser({
|
||||
id: newUser.user.id,
|
||||
name: recipientEmail.split("@")[0],
|
||||
language: "fr",
|
||||
});
|
||||
|
||||
// Send welcome email to the new user
|
||||
await transporter.sendMail({
|
||||
from: `${senderEmail} via XTablo <noreply@xtablo.com>`,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { createClient, type SupabaseClient, type User } from "@supabase/supabase
|
|||
import type { Context, MiddlewareHandler, Next } from "hono";
|
||||
import { createMiddleware } from "hono/factory";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import { StreamChat } from "stream-chat";
|
||||
import { Stripe } from "stripe";
|
||||
import { type AppConfig } from "../config.js";
|
||||
import { authenticateFromHeader } from "../helpers/auth.js";
|
||||
|
|
@ -25,9 +24,6 @@ export type Middlewares = {
|
|||
Variables: { supabase: SupabaseClient; user: User };
|
||||
Bindings: { user: User };
|
||||
}>;
|
||||
streamChatMiddleware: MiddlewareHandler<{
|
||||
Variables: { streamServerClient: StreamChat };
|
||||
}>;
|
||||
r2Middleware: MiddlewareHandler<{
|
||||
Variables: { s3_client: S3Client };
|
||||
}>;
|
||||
|
|
@ -168,15 +164,6 @@ export class MiddlewareManager {
|
|||
await next();
|
||||
});
|
||||
|
||||
const streamChatMiddleware = createMiddleware(async (c: Context, next: Next) => {
|
||||
const serverClient = StreamChat.getInstance(
|
||||
config.STREAM_CHAT_API_KEY,
|
||||
config.STREAM_CHAT_API_SECRET
|
||||
);
|
||||
c.set("streamServerClient", serverClient);
|
||||
await next();
|
||||
});
|
||||
|
||||
const r2Middleware = createMiddleware(async (c: Context, next: Next) => {
|
||||
const s3 = new S3Client({
|
||||
region: "auto",
|
||||
|
|
@ -255,7 +242,6 @@ export class MiddlewareManager {
|
|||
basicAuthMiddleware,
|
||||
authMiddleware,
|
||||
maybeAuthenticatedMiddleware,
|
||||
streamChatMiddleware,
|
||||
r2Middleware,
|
||||
regularUserCheckMiddleware,
|
||||
billingCheckoutAccessMiddleware,
|
||||
|
|
@ -282,10 +268,6 @@ export class MiddlewareManager {
|
|||
return this.middlewares.maybeAuthenticatedMiddleware;
|
||||
}
|
||||
|
||||
get streamChat() {
|
||||
return this.middlewares.streamChatMiddleware;
|
||||
}
|
||||
|
||||
get r2() {
|
||||
return this.middlewares.r2Middleware;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ export const getMainRouter = (config: AppConfig) => {
|
|||
mainRouter.use(middlewareManager.supabase);
|
||||
|
||||
// Apply remaining middlewares after public routes
|
||||
mainRouter.use(middlewareManager.streamChat);
|
||||
mainRouter.use(middlewareManager.r2);
|
||||
mainRouter.use(middlewareManager.transporter);
|
||||
mainRouter.use(middlewareManager.stripe);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const factory = createFactory<MaybeAuthEnv>();
|
|||
|
||||
const bookSlot = factory.createHandlers(async (c) => {
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const transporter = c.get("transporter");
|
||||
const maybeUser = c.get("user");
|
||||
|
||||
|
|
@ -55,7 +54,6 @@ const bookSlot = factory.createHandlers(async (c) => {
|
|||
// Create a temporary user for the booking
|
||||
const result = await createInvitedUser(
|
||||
supabase,
|
||||
streamServerClient,
|
||||
transporter,
|
||||
data.user_details.email,
|
||||
ownerData.email,
|
||||
|
|
@ -220,28 +218,6 @@ const bookSlot = factory.createHandlers(async (c) => {
|
|||
return c.json({ error: tabloAccessError.message }, 500);
|
||||
}
|
||||
|
||||
// Create Stream chat channel with the owner as creator
|
||||
const { data: organizationMembers, error: organizationMembersError } = await supabase
|
||||
.from("profiles")
|
||||
.select("id")
|
||||
.eq("organization_id", ownerOrganizationId);
|
||||
|
||||
if (organizationMembersError) {
|
||||
return c.json({ error: "Failed to load organization members" }, 500);
|
||||
}
|
||||
|
||||
const channelMembers = Array.from(
|
||||
new Set((organizationMembers || []).map((member) => member.id).concat(bookerUserDataTyped.id))
|
||||
);
|
||||
|
||||
const channel = streamServerClient.channel("messaging", tabloData.id, {
|
||||
// @ts-ignore
|
||||
name: tabloData.name,
|
||||
created_by_id: ownerId,
|
||||
members: channelMembers,
|
||||
});
|
||||
await channel.create();
|
||||
|
||||
const newEvent: TablesInsert<"events"> = {
|
||||
description: eventTypeConfig.description || "",
|
||||
end_time: data.event_details.end_time || "",
|
||||
|
|
@ -258,12 +234,6 @@ const bookSlot = factory.createHandlers(async (c) => {
|
|||
return c.json({ error: "Failed to create event" }, 500);
|
||||
}
|
||||
|
||||
// Send a welcome message to the channel
|
||||
await channel.sendMessage({
|
||||
text: `🎉 Bienvenue dans votre nouveau tablo "${tabloData.name}" ! Votre rendez-vous "${newEvent.title}" est confirmé pour le ${newEvent.start_date} de ${newEvent.start_time} à ${newEvent.end_time}.`,
|
||||
user_id: ownerId,
|
||||
});
|
||||
|
||||
// Send email notifications to both owner and invited user
|
||||
// Send email to the owner
|
||||
await transporter.sendMail({
|
||||
|
|
|
|||
|
|
@ -18,83 +18,6 @@ type PostTablo = Omit<TabloInsert, "owner_id" | "organization_id"> & {
|
|||
|
||||
const factory = createFactory<AuthEnv>();
|
||||
|
||||
const isAlreadyMemberError = (error: unknown): boolean => {
|
||||
if (!error) return false;
|
||||
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
||||
return (
|
||||
message.includes("already a member") ||
|
||||
message.includes("already member") ||
|
||||
message.includes("member already exists")
|
||||
);
|
||||
};
|
||||
|
||||
const upsertStreamUserFromProfile = async (
|
||||
supabase: AuthEnv["Variables"]["supabase"],
|
||||
streamServerClient: AuthEnv["Variables"]["streamServerClient"],
|
||||
userId: string
|
||||
) => {
|
||||
const { data: profile } = await supabase
|
||||
.from("profiles")
|
||||
.select("name")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
|
||||
await streamServerClient.upsertUser({
|
||||
id: userId,
|
||||
name: profile?.name ?? "",
|
||||
language: "fr",
|
||||
});
|
||||
};
|
||||
|
||||
const ensureTabloChannelMember = async (
|
||||
supabase: AuthEnv["Variables"]["supabase"],
|
||||
streamServerClient: AuthEnv["Variables"]["streamServerClient"],
|
||||
tabloId: string,
|
||||
userId: string
|
||||
) => {
|
||||
const channel = streamServerClient.channel("messaging", tabloId);
|
||||
|
||||
try {
|
||||
await channel.addMembers([userId]);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (isAlreadyMemberError(error)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { data: tablo } = await supabase
|
||||
.from("tablos")
|
||||
.select("name, owner_id")
|
||||
.eq("id", tabloId)
|
||||
.maybeSingle();
|
||||
|
||||
const { data: accessRows } = await supabase
|
||||
.from("tablo_access")
|
||||
.select("user_id")
|
||||
.eq("tablo_id", tabloId)
|
||||
.eq("is_active", true);
|
||||
|
||||
const members = Array.from(new Set((accessRows || []).map((row) => row.user_id).concat(userId)));
|
||||
|
||||
const channelToCreate = streamServerClient.channel("messaging", tabloId, {
|
||||
// @ts-ignore
|
||||
name: tablo?.name ?? "Tablo",
|
||||
created_by_id: tablo?.owner_id ?? userId,
|
||||
members,
|
||||
});
|
||||
|
||||
try {
|
||||
await channelToCreate.create();
|
||||
} catch (error) {
|
||||
if (isAlreadyMemberError(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.addMembers([userId]);
|
||||
}
|
||||
};
|
||||
|
||||
const createTablo = (middlewareManager: ReturnType<typeof MiddlewareManager.getInstance>) =>
|
||||
factory.createHandlers(
|
||||
middlewareManager.regularUserCheck,
|
||||
|
|
@ -134,28 +57,6 @@ const createTablo = (middlewareManager: ReturnType<typeof MiddlewareManager.getI
|
|||
|
||||
const tabloData = insertedTablo as Tables<"tablos">;
|
||||
|
||||
const { data: organizationMembers, error: membersError } = await supabase
|
||||
.from("profiles")
|
||||
.select("id")
|
||||
.eq("organization_id", profile.organization_id);
|
||||
|
||||
if (membersError) {
|
||||
return c.json({ error: "Failed to load organization members" }, 500);
|
||||
}
|
||||
|
||||
const channelMembers = Array.from(
|
||||
new Set((organizationMembers || []).map((member) => member.id).concat(user.id))
|
||||
);
|
||||
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const channel = streamServerClient.channel("messaging", tabloData.id, {
|
||||
// @ts-ignore
|
||||
name: tabloData.name,
|
||||
created_by_id: user.id,
|
||||
members: channelMembers,
|
||||
});
|
||||
await channel.create();
|
||||
|
||||
if (typedPayload.events) {
|
||||
const eventsToInsert = typedPayload.events.map((event) => ({
|
||||
...event,
|
||||
|
|
@ -173,7 +74,6 @@ const updateTablo = (middlewareManager: ReturnType<typeof MiddlewareManager.getI
|
|||
factory.createHandlers(middlewareManager.regularUserCheck, async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const data = await c.req.json();
|
||||
|
||||
const { id, ...tablo } = data;
|
||||
|
|
@ -190,7 +90,7 @@ const updateTablo = (middlewareManager: ReturnType<typeof MiddlewareManager.getI
|
|||
return c.json({ error: "You are not authorized to update this tablo" }, 403);
|
||||
}
|
||||
|
||||
const { data: update, error } = await supabase
|
||||
const { error } = await supabase
|
||||
.from("tablos")
|
||||
.update(tablo)
|
||||
.eq("id", id)
|
||||
|
|
@ -201,28 +101,12 @@ const updateTablo = (middlewareManager: ReturnType<typeof MiddlewareManager.getI
|
|||
return c.json({ error: error.message }, 500);
|
||||
}
|
||||
|
||||
const updatedTablo = update as Tables<"tablos">;
|
||||
const isUpdatingName = tablo.name !== undefined;
|
||||
|
||||
if (isUpdatingName) {
|
||||
const channel = streamServerClient.channel("messaging", updatedTablo.id);
|
||||
try {
|
||||
await channel.update({
|
||||
// @ts-ignore
|
||||
name: updatedTablo.name,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error updating channel", error);
|
||||
}
|
||||
}
|
||||
|
||||
return c.json({ message: "Tablo updated successfully" });
|
||||
});
|
||||
|
||||
const deleteTablo = factory.createHandlers(async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const data = await c.req.json();
|
||||
|
||||
const { id } = data;
|
||||
|
|
@ -270,13 +154,6 @@ const deleteTablo = factory.createHandlers(async (c) => {
|
|||
return c.json({ error: error.message }, 500);
|
||||
}
|
||||
|
||||
const channel = streamServerClient.channel("messaging", id);
|
||||
try {
|
||||
await channel.delete();
|
||||
} catch (error) {
|
||||
console.error("error deleting channel", error);
|
||||
}
|
||||
|
||||
return c.json({ message: "Tablo deleted successfully" });
|
||||
});
|
||||
|
||||
|
|
@ -288,7 +165,6 @@ const inviteToTablo = (
|
|||
const transporter = c.get("transporter");
|
||||
const sender = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
|
||||
const tabloId = c.req.param("tabloId");
|
||||
const { email: recipientmail } = await c.req.json();
|
||||
|
|
@ -355,7 +231,6 @@ const inviteToTablo = (
|
|||
// Create a new invited user and add them to the tablo
|
||||
const result = await createInvitedUser(
|
||||
supabase,
|
||||
streamServerClient,
|
||||
transporter,
|
||||
recipientEmail,
|
||||
sender.email,
|
||||
|
|
@ -381,13 +256,6 @@ const inviteToTablo = (
|
|||
return c.json({ error: tabloAccessError.message }, 500);
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureTabloChannelMember(supabase, streamServerClient, tabloId, result.userId);
|
||||
} catch (streamError) {
|
||||
console.error("error adding temporary invited user to channel", streamError);
|
||||
return c.json({ error: "Failed to sync chat access for invited user" }, 500);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
message: "User created and invite sent successfully",
|
||||
});
|
||||
|
|
@ -438,7 +306,6 @@ const cancelPendingInvite = (middlewareManager: ReturnType<typeof MiddlewareMana
|
|||
factory.createHandlers(middlewareManager.regularUserCheck, async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const tabloId = c.req.param("tabloId");
|
||||
const inviteId = Number(c.req.param("inviteId"));
|
||||
|
||||
|
|
@ -513,13 +380,6 @@ const cancelPendingInvite = (middlewareManager: ReturnType<typeof MiddlewareMana
|
|||
if (revokeAccessError) {
|
||||
return c.json({ error: revokeAccessError.message }, 500);
|
||||
}
|
||||
|
||||
try {
|
||||
const channel = streamServerClient.channel("messaging", tabloId);
|
||||
await channel.removeMembers([invitedProfile.id]);
|
||||
} catch (error) {
|
||||
console.error("error removing cancelled invitee from channel", error);
|
||||
}
|
||||
}
|
||||
|
||||
return c.json({ message: "Invite cancelled successfully" });
|
||||
|
|
@ -573,7 +433,6 @@ const acceptInviteById = (middlewareManager: ReturnType<typeof MiddlewareManager
|
|||
factory.createHandlers(middlewareManager.regularUserCheck, async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const inviteId = Number(c.req.param("inviteId"));
|
||||
|
||||
if (!Number.isInteger(inviteId) || inviteId <= 0) {
|
||||
|
|
@ -598,13 +457,6 @@ const acceptInviteById = (middlewareManager: ReturnType<typeof MiddlewareManager
|
|||
return c.json({ error: "You are not authorized to accept this invite" }, 403);
|
||||
}
|
||||
|
||||
try {
|
||||
await upsertStreamUserFromProfile(supabase, streamServerClient, user.id);
|
||||
} catch (error) {
|
||||
console.error("error upserting joining user to stream", error);
|
||||
return c.json({ error: "Failed to provision chat user" }, 500);
|
||||
}
|
||||
|
||||
const { error: tabloAccessError } = await supabase.from("tablo_access").insert({
|
||||
tablo_id: inviteData.tablo_id,
|
||||
user_id: user.id,
|
||||
|
|
@ -621,13 +473,6 @@ const acceptInviteById = (middlewareManager: ReturnType<typeof MiddlewareManager
|
|||
|
||||
await supabase.from("tablo_invites").update({ is_pending: false }).eq("id", inviteData.id);
|
||||
|
||||
try {
|
||||
await ensureTabloChannelMember(supabase, streamServerClient, inviteData.tablo_id, user.id);
|
||||
} catch (error) {
|
||||
console.error("error adding member to channel", error);
|
||||
return c.json({ error: "Failed to sync chat access for this tablo" }, 500);
|
||||
}
|
||||
|
||||
return c.json({ tablo_id: inviteData.tablo_id });
|
||||
});
|
||||
|
||||
|
|
@ -636,7 +481,6 @@ const joinTablo = factory.createHandlers(async (c) => {
|
|||
|
||||
const joiner = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
|
||||
const { data: inviteData, error } = await supabase
|
||||
.from("tablo_invites")
|
||||
|
|
@ -657,13 +501,6 @@ const joinTablo = factory.createHandlers(async (c) => {
|
|||
|
||||
const { id: invite_id, tablo_id, invited_by } = inviteData;
|
||||
|
||||
try {
|
||||
await upsertStreamUserFromProfile(supabase, streamServerClient, joiner.id);
|
||||
} catch (error) {
|
||||
console.error("error upserting joining user to stream", error);
|
||||
return c.json({ error: "Failed to provision chat user" }, 500);
|
||||
}
|
||||
|
||||
const { error: tabloAccessError } = await supabase.from("tablo_access").insert({
|
||||
tablo_id,
|
||||
user_id: joiner.id,
|
||||
|
|
@ -686,13 +523,6 @@ const joinTablo = factory.createHandlers(async (c) => {
|
|||
// Mark invite as accepted instead of deleting (maintains audit trail)
|
||||
await supabase.from("tablo_invites").update({ is_pending: false }).eq("id", invite_id);
|
||||
|
||||
try {
|
||||
await ensureTabloChannelMember(supabase, streamServerClient, tablo_id, joiner.id);
|
||||
} catch (error) {
|
||||
console.error("error adding member to channel", error);
|
||||
return c.json({ error: "Failed to sync chat access for this tablo" }, 500);
|
||||
}
|
||||
|
||||
return c.json({ tablo_id });
|
||||
});
|
||||
|
||||
|
|
@ -748,12 +578,8 @@ const getTabloMembers = factory.createHandlers(async (c) => {
|
|||
const leaveTablo = factory.createHandlers(async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const { tablo_id } = await c.req.json();
|
||||
|
||||
const channel = streamServerClient.channel("messaging", tablo_id);
|
||||
await channel.removeMembers([user.id]);
|
||||
|
||||
const { error } = await supabase
|
||||
.from("tablo_access")
|
||||
.update({ is_active: false })
|
||||
|
|
@ -872,7 +698,6 @@ export const getTabloRouter = (config: AppConfig) => {
|
|||
|
||||
tabloRouter.use(middlewareManager.supabase);
|
||||
tabloRouter.use(middlewareManager.auth);
|
||||
tabloRouter.use(middlewareManager.streamChat);
|
||||
|
||||
tabloRouter.post("/create", ...createTablo(middlewareManager));
|
||||
tabloRouter.patch("/update", ...updateTablo(middlewareManager));
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ const syncCalendars = factory.createHandlers(async (c) => {
|
|||
|
||||
const syncTabloNames = factory.createHandlers(async (c) => {
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
|
||||
const fifteenMinutesInMilliseconds = 1000 * 60 * 15;
|
||||
|
||||
|
|
@ -54,18 +53,6 @@ const syncTabloNames = factory.createHandlers(async (c) => {
|
|||
|
||||
const tablosData = data as { id: string; name: string }[];
|
||||
|
||||
tablosData.forEach(async (tablo) => {
|
||||
const channel = streamServerClient.channel("messaging", tablo.id);
|
||||
try {
|
||||
await channel.update({
|
||||
// @ts-ignore
|
||||
name: tablo.name,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`error updating channel, tablo id: ${tablo.id}, error: ${error}`);
|
||||
}
|
||||
});
|
||||
|
||||
return c.json({ message: `Synced ${tablosData.length} tablo names` });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,30 +11,9 @@ const factory = createFactory<AuthEnv>();
|
|||
const isMissingRelationError = (code: string | undefined) =>
|
||||
code === "42P01" || code === "PGRST205";
|
||||
|
||||
const signUpToStream = factory.createHandlers(async (c) => {
|
||||
const { id } = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
|
||||
const { data } = await supabase.from("profiles").select("*").eq("id", id).single();
|
||||
|
||||
const user = data as Tables<"profiles">;
|
||||
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
await streamServerClient.upsertUser({
|
||||
id,
|
||||
name: user.name ?? "",
|
||||
language: "fr",
|
||||
});
|
||||
|
||||
return c.json({
|
||||
message: "User signed up to stream",
|
||||
});
|
||||
});
|
||||
|
||||
const getMe = factory.createHandlers(async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
|
||||
const { data, error } = await supabase.from("profiles").select("*").eq("id", user.id).single();
|
||||
|
||||
|
|
@ -60,14 +39,7 @@ const getMe = factory.createHandlers(async (c) => {
|
|||
effectivePlan = organizationPlan;
|
||||
}
|
||||
|
||||
const user_id = data.id;
|
||||
const token = streamServerClient.createToken(user_id);
|
||||
|
||||
return c.json({
|
||||
...userData,
|
||||
plan: effectivePlan,
|
||||
streamToken: token,
|
||||
});
|
||||
return c.json({ ...userData, plan: effectivePlan });
|
||||
});
|
||||
|
||||
const markTemporary = factory.createHandlers(async (c) => {
|
||||
|
|
@ -515,7 +487,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
|
|||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const transporter = c.get("transporter");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const body = await c.req.json();
|
||||
const rawEmail = typeof body?.email === "string" ? body.email : "";
|
||||
const recipientEmail = rawEmail.trim().toLowerCase();
|
||||
|
|
@ -613,7 +584,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
|
|||
|
||||
const invitedUser = await createInvitedUser(
|
||||
supabase,
|
||||
streamServerClient,
|
||||
transporter,
|
||||
recipientEmail,
|
||||
senderProfile.email,
|
||||
|
|
@ -673,15 +643,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
|
|||
}
|
||||
}
|
||||
|
||||
for (const tablo of organizationTablos || []) {
|
||||
const channel = streamServerClient.channel("messaging", tablo.id);
|
||||
try {
|
||||
await channel.addMembers([invitedUser.userId]);
|
||||
} catch (error) {
|
||||
console.error("Failed to add invited user to Stream channel:", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldOrganizationId && oldOrganizationId !== organizationId) {
|
||||
const { count: oldOrgMembersCount } = await supabase
|
||||
.from("profiles")
|
||||
|
|
@ -717,7 +678,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
|
|||
const removeOrganizationMember = factory.createHandlers(async (c) => {
|
||||
const user = c.get("user");
|
||||
const supabase = c.get("supabase");
|
||||
const streamServerClient = c.get("streamServerClient");
|
||||
const memberId = c.req.param("memberId");
|
||||
|
||||
if (!memberId) {
|
||||
|
|
@ -826,14 +786,6 @@ const removeOrganizationMember = factory.createHandlers(async (c) => {
|
|||
return c.json({ error: "Failed to revoke member tablo permissions" }, 500);
|
||||
}
|
||||
|
||||
for (const tabloId of tabloIds) {
|
||||
try {
|
||||
const channel = streamServerClient.channel("messaging", tabloId);
|
||||
await channel.removeMembers([memberId]);
|
||||
} catch (error) {
|
||||
console.error("Failed to remove organization member from Stream channel:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { error: inviteCleanupError } = await supabase
|
||||
|
|
@ -852,7 +804,6 @@ const removeOrganizationMember = factory.createHandlers(async (c) => {
|
|||
export const getUserRouter = () => {
|
||||
const userRouter = new Hono();
|
||||
|
||||
userRouter.post("/sign-up-to-stream", ...signUpToStream);
|
||||
userRouter.get("/me", ...getMe);
|
||||
userRouter.post("/mark-temporary", ...markTemporary);
|
||||
userRouter.post("/profile/avatar", ...uploadAvatar);
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ export type Secrets = {
|
|||
r2AccessKeyId: string;
|
||||
r2SecretAccessKey: string;
|
||||
// Env dependent
|
||||
streamChatApiSecret: string;
|
||||
stripeSecretKey: string;
|
||||
stripeWebhookSecret: string;
|
||||
// Staging
|
||||
streamChatApiSecretStaging: string;
|
||||
stripeSecretKeyStaging: string;
|
||||
stripeWebhookSecretStaging: string;
|
||||
};
|
||||
|
|
@ -50,11 +48,9 @@ export async function loadSecrets(): Promise<Secrets> {
|
|||
r2SecretAccessKey: await fetchSecret("r2-secret-access-key"),
|
||||
// Env dependent
|
||||
// Staging
|
||||
streamChatApiSecretStaging: await fetchSecret("stream-chat-api-secret-staging"),
|
||||
stripeSecretKeyStaging: await fetchSecret("stripe-secret-key-staging"),
|
||||
stripeWebhookSecretStaging: await fetchSecret("stripe-webhook-secret-staging"),
|
||||
// Production
|
||||
streamChatApiSecret: await fetchSecret("stream-chat-api-secret"),
|
||||
stripeSecretKey: await fetchSecret("stripe-secret-key"),
|
||||
stripeWebhookSecret: await fetchSecret("stripe-webhook-secret"),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { StripeSync } from "@supabase/stripe-sync-engine";
|
|||
import type { SupabaseClient, User } from "@supabase/supabase-js";
|
||||
import type { Hono } from "hono";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import type { StreamChat } from "stream-chat";
|
||||
import type Stripe from "stripe";
|
||||
|
||||
/**
|
||||
|
|
@ -12,7 +11,6 @@ import type Stripe from "stripe";
|
|||
export type BaseEnv = {
|
||||
Variables: {
|
||||
supabase: SupabaseClient;
|
||||
streamServerClient: StreamChat;
|
||||
s3_client: S3Client;
|
||||
transporter: Transporter;
|
||||
stripe: Stripe;
|
||||
|
|
|
|||
Loading…
Reference in a new issue