schema mostly done. functions mostly done. working on APIs now

This commit is contained in:
Gabriel Brown 2024-09-27 16:29:54 -05:00
parent 2a5acf9325
commit edffe130a5
18 changed files with 7512 additions and 174 deletions

6681
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
'use server';
import { NextResponse } from 'next/server';
import { getMessage } from '~/server/functions';
export const GET = async (request: Request) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get('apiKey');
if (apiKey !== process.env.API_KEY) {
console.log('Invalid API Key');
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const userId = url.searchParams.get('userId') ?? '2';
const message = await getMessage(parseInt(userId));
return NextResponse.json(message);
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/getMessage?apiKey=I_Love_Madeline&userId=2

View File

@ -1,21 +0,0 @@
'use server';
import { NextResponse } from 'next/server';
import { getUsers } from '~/server/functions';
export const GET = async (request: Request) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get('apiKey');
if (apiKey !== process.env.API_KEY) {
console.log('Invalid API Key');
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const users = await getUsers();
return NextResponse.json(users);
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/getUsers?apiKey=I_Love_Madeline

View File

@ -0,0 +1,39 @@
"use server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { sendMessage } from "~/server/functions";
type sendMessageRequest = {
senderId: number;
receiverId: number;
content: string;
mediaUrl?: string;
mediaType?: "text" | "image" | "video" | "audio" | "file" | "link";
};
export const POST = async (request: NextRequest) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
const body = await request.json() as sendMessageRequest;
const {
senderId,
receiverId,
content,
mediaUrl = "",
mediaType = "text",
} = body;
if (!senderId || !receiverId || !content) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
const message = await sendMessage(senderId, receiverId, content, mediaUrl, mediaType);
return NextResponse.json(message, { status: 201 });
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};

View File

@ -0,0 +1,41 @@
"use server";
import { NextResponse } from "next/server";
import { createRelationshipRequest } from "~/server/functions";
interface CreateRequestRequest {
apiKey: string;
userId: number;
targetUserId: number;
}
export async function POST(request: Request) {
try {
const { apiKey, userId, targetUserId } = await request.json() as CreateRequestRequest;
console.log("Received request:", { apiKey, userId, targetUserId });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!userId || !targetUserId) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
console.log("Creating relationship request for user:", userId);
const result = await createRelationshipRequest(userId, targetUserId);
if (result) {
console.log("Relationship request created successfully");
return NextResponse.json({ result }, { status: 200 });
} else {
throw new Error("Failed to create relationship request");
}
} catch (error) {
console.error("Error in createRequest:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error creating relationship request" }, { status: 500 });
}
}

View File

@ -0,0 +1,40 @@
"use server";
import { NextResponse } from "next/server";
import { deleteRelationship } from "~/server/functions";
interface DeleteRelationshipRequest {
apiKey: string;
relationshipId: number;
}
export async function POST(request: Request) {
try {
const { apiKey, relationshipId } = await request.json() as DeleteRelationshipRequest;
console.log("Received request:", { apiKey, relationshipId });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!relationshipId) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
console.log("Deleting relationship:", relationshipId);
const result = await deleteRelationship(relationshipId);
if (result.success) {
console.log("Relationship deleted successfully");
return NextResponse.json({ result }, { status: 200 });
} else {
throw new Error("Failed to delete relationship");
}
} catch (error) {
console.error("Error in deleteRelationship:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error deleting relationship" }, { status: 500 });
}
}

View File

@ -0,0 +1,41 @@
"use server";
import { NextResponse } from "next/server";
import { updateRelationshipRequest } from "~/server/functions";
interface UpdateRequestRequest {
apiKey: string;
relationshipId: number;
status: 'accepted' | 'rejected';
}
export async function POST(request: Request) {
try {
const { apiKey, relationshipId, status } = await request.json() as UpdateRequestRequest;
console.log("Received request:", { apiKey, relationshipId, status });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!relationshipId || !status) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
console.log("Updating relationship request:", relationshipId);
const result = await updateRelationshipRequest(relationshipId, status);
if (result.success) {
console.log("Relationship request updated successfully");
return NextResponse.json({ result }, { status: 200 });
} else {
throw new Error("Failed to update relationship request");
}
} catch (error) {
console.error("Error in updateRequest:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error updating relationship request" }, { status: 500 });
}
}

View File

@ -1,23 +0,0 @@
"use server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { setCountdown } from "~/server/functions";
export const POST = async (request: NextRequest) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const countdown = url.searchParams.get("countdown") ?? "2023-01-01T00:00:00.000Z";
await setCountdown(new Date(countdown));
return NextResponse.json({ message: "Countdown set successfully" });
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/setCountdown?apiKey=I_Love_Madeline&countdown=2024-09-20T12:00:00.000Z

View File

@ -0,0 +1,31 @@
"use server";
import { NextResponse } from "next/server";
import { changePassword } from "~/server/functions";
type Data = {
apiKey: string;
userId: number;
oldPassword: string;
newPassword: string;
};
export const POST = async (request: Request) => {
try {
const { apiKey, userId, oldPassword, newPassword } = await request.json() as Data;
console.log("Received request:", { apiKey, userId, oldPassword, newPassword });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
console.log("Changing password for user:", userId);
await changePassword(userId, oldPassword, newPassword);
console.log("Password changed successfully");
return NextResponse.json({ message: "Password changed successfully" });
} catch (error) {
console.error("Error in changePassword:", error);
return NextResponse.json({ message: "Error changing password" }, { status: 500 });
}
};

View File

@ -0,0 +1,65 @@
"use server";
import { NextResponse } from "next/server";
import { createUser } from "~/server/functions";
type CreateUserRequest = {
username: string;
email: string;
passwordHash: string;
name: string;
pfpURL?: string;
pushToken?: string;
};
export async function POST(request: Request) {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
// Parse the request body instead of using URL parameters for POST requests
const body = await request.json() as CreateUserRequest;
// Destructure the body with default values for optional fields
const {
username,
email,
passwordHash,
name,
pfpURL = "",
pushToken = ""
} = body;
// Validate required fields
if (!username || !email || !passwordHash || !name) {
return NextResponse.json(
{ message: "Missing required fields" },
{ status: 400 }
);
}
const user = await createUser(
username,
email,
passwordHash,
name,
pfpURL,
pushToken
);
return NextResponse.json(user, { status: 201 });
} catch (error) {
console.error("Error creating user:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json(
{ message: "An unexpected error occurred" },
{ status: 500 }
);
}
}

View File

@ -1,9 +1,8 @@
"use server"; "use server";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import { getUserById } from "~/server/functions";
import { setMessage } from "~/server/functions";
export const POST = async (request: NextRequest) => { export const GET = async (request: Request) => {
try { try {
const url = new URL(request.url); const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey"); const apiKey = url.searchParams.get("apiKey");
@ -12,13 +11,11 @@ export const POST = async (request: NextRequest) => {
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 }); return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else { } else {
const userId = url.searchParams.get("userId") ?? "2"; const userId = url.searchParams.get("userId") ?? "2";
const message = url.searchParams.get("message") ?? "Test"; const user = await getUserById(parseInt(userId));
await setMessage(parseInt(userId), message); return NextResponse.json(user);
return NextResponse.json({ message: "Message set successfully" });
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 }); return NextResponse.json({ message: "Error" }, { status: 500 });
} }
}; };
// localhost:3000/api/setMessage?apiKey=I_Love_Madeline&userId=2&message=HelloWorld

View File

@ -1,21 +1,21 @@
'use server'; "use server";
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { getCountdown } from '~/server/functions'; import { getUserByUsername } from "~/server/functions";
export const GET = async (request: Request) => { export const GET = async (request: Request) => {
try { try {
const url = new URL(request.url); const url = new URL(request.url);
const apiKey = url.searchParams.get('apiKey'); const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) { if (apiKey !== process.env.API_KEY) {
console.log('Invalid API Key'); console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 }); return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else { } else {
const countdown = await getCountdown(); const username = url.searchParams.get("username") ?? "2";
return NextResponse.json(countdown); const user = await getUserByUsername(username);
return NextResponse.json(user);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 }); return NextResponse.json({ message: "Error" }, { status: 500 });
} }
}; };
// localhost:3000/api/getCountdown?apiKey=I_Love_Madeline

View File

@ -0,0 +1,41 @@
"use server";
import { NextResponse } from "next/server";
import { userLogin } from "~/server/functions";
interface LoginRequest {
apiKey: string;
username: string;
password: string;
}
export async function POST(request: Request) {
try {
const { apiKey, username, password } = await request.json() as LoginRequest;
console.log("Received request:", { apiKey, username, password });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!username || !password) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
console.log("Logging in user:", username);
const result = await userLogin(username, password);
if (result) {
console.log("User logged in successfully");
return NextResponse.json({ result }, { status: 200 });
} else {
throw new Error("Failed to log in user");
}
} catch (error) {
console.error("Error in login:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error logging in user" }, { status: 500 });
}
}

View File

@ -0,0 +1,41 @@
"use server";
import { NextResponse } from "next/server";
import { updateUserPFP } from "~/server/functions";
interface UpdatePfpRequest {
apiKey: string;
userId: number;
pfpURL: string;
}
export async function POST(request: Request) {
try {
const { apiKey, userId, pfpURL } = await request.json() as UpdatePfpRequest;
console.log("Received request:", { apiKey, userId, pfpURL });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!userId || !pfpURL) {
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
}
console.log("Updating profile picture for user:", userId);
const result = await updateUserPFP(userId, pfpURL);
if (result.success) {
console.log("Profile picture updated successfully");
return NextResponse.json({ message: "Profile picture updated successfully" });
} else {
throw new Error("Failed to update profile picture");
}
} catch (error) {
console.error("Error in updatePFP:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error updating profile picture" }, { status: 500 });
}
}

View File

@ -0,0 +1,36 @@
"use server";
import { NextResponse } from "next/server";
import { updateUserPushToken } from "~/server/functions";
interface UpdatePushTokenRequest {
apiKey: string;
userId: number;
pushToken: string;
}
export async function POST(request: Request) {
try {
const { apiKey, userId, pushToken } = await request.json() as UpdatePushTokenRequest;
console.log("Received request:", { apiKey, userId, pushToken });
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
}
if (!userId || !pushToken)
return NextResponse.json({ message: "Missing required fields" }, { status: 400 });
console.log("Updating push token for user:", userId);
const result = await updateUserPushToken(userId, pushToken);
if (result.success) {
console.log("Push token updated successfully");
return NextResponse.json({ message: "Push token updated successfully" });
} else {
throw new Error("Failed to update push token");
}
} catch (error) {
console.error("Error in updatePushToken:", error);
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: "Error updating push token" }, { status: 500 });
}
}

View File

@ -4,27 +4,91 @@ import {
serial, serial,
timestamp, timestamp,
varchar, varchar,
text,
integer,
boolean,
pgEnum
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
export const createTable = pgTableCreator((name) => `${name}`); export const createTable = pgTableCreator((name) => `${name}`);
export const statusEnum = pgEnum("status", ["pending", "accepted", "rejected"]);
export const messageTypeEnum = pgEnum("message_types", ["text", "image", "video", "audio", "file", "link"]);
export const users = createTable( export const users = createTable(
"user", "user",
{ {
id: serial("id").primaryKey(), id: serial("id").primaryKey(),
name: varchar("name", { length: 256 }), username: varchar("username", { length: 50 }).unique().notNull(),
message: varchar("message", { length: 256 }), email: varchar("email", { length: 255 }).unique().notNull(),
passwordHash: varchar("password_hash", {length: 255}).notNull(),
name: varchar("name", { length: 100 }),
pfpURL: varchar("pfp_url", { length: 255 }),
pushToken: varchar("pushToken", { length: 256 }), pushToken: varchar("pushToken", { length: 256 }),
lastLogin: timestamp("last_login", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }) createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`) .default(sql`CURRENT_TIMESTAMP`)
.notNull(), .notNull(),
}, },
); );
export const countdown = createTable( export const relationships = createTable(
"relationship",
{
id: serial("id").primaryKey(),
title: varchar("title", { length: 100 }).default("Relationship").notNull(),
status: statusEnum("status").default("pending").notNull(),
relationshipStartDate: timestamp("relationship_start_date", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`).notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`).notNull(),
},
);
export const userRelationships = createTable(
"user_relationship",
{
id: serial("id").primaryKey(),
userId: integer("user_id").references(() => users.id).notNull(),
relationshipId: integer("relationship_id").references(() => relationships.id).notNull(),
},
);
export const messages = createTable(
"message",
{
id: serial("id").primaryKey(),
senderId: integer("sender_id").references(() => users.id).notNull(),
receiverId: integer("receiver_id").references(() => users.id).notNull(),
content: text("content").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
isRead: boolean("is_read").default(false).notNull(),
},
);
export const messageMedia = createTable(
"message_media",
{
id: serial("id").primaryKey(),
messageId: integer("message_id").references(() => messages.id).notNull(),
type: messageTypeEnum("type").notNull(),
mediaUrl: varchar("url", { length: 255 }).notNull(),
},
);
export const countdowns = createTable(
"countdown", "countdown",
{ {
id: serial("id").primaryKey(), id: serial("id").primaryKey(),
relationshipId: integer("relationship_id").references(() => relationships.id).notNull(),
title: varchar("title", { length: 100 })
.default("Countdown to Next Visit")
.notNull(),
date: timestamp("date", { withTimezone: true }), date: timestamp("date", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
}, },
); );

View File

@ -1,130 +1,417 @@
import 'server-only'; import 'server-only';
import { db } from '~/server/db'; import { db } from '~/server/db';
import * as schema from '~/server/db/schema'; import * as schema from '~/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq, and, or } from 'drizzle-orm';
import { pgEnum } from 'drizzle-orm/pg-core';
export const getUsers = async () => { // --- Helper Functions --- //
try {
const result = await db.select({
id: schema.users.id,
name: schema.users.name,
message: schema.users.message,
}).from(schema.users);
return result;
} catch (error) {
console.error("Error fetching users", error);
throw new Error("Failed to fetch users");
}
};
export const getUser = async (userId: number) => { /**
* Ensure the user exists based on their userId.
* Throws an error if the user doesn't exist.
*/
export const ensureUserExists = async (userId: number) => {
try { try {
const result = await db.select({ const user = await db.select().from(schema.users)
id: schema.users.id,
name: schema.users.name,
message: schema.users.message,
pushToken: schema.users.pushToken,
}).from(schema.users)
.where(eq(schema.users.id, userId)); .where(eq(schema.users.id, userId));
return result;
if (user.length === 0) {
throw new Error('User does not exist');
}
return user[0]; // Return user for further use if needed
} catch (error) { } catch (error) {
console.error("Error fetching user", error); if (error instanceof Error) {
throw new Error("Failed to fetch user"); throw new Error(`Error checking user: ${error.message}`);
} else {
throw new Error("Unknown error occurred while checking user");
}
} }
}; };
export const getMessage = async (userId: number) => { /**
* Ensure relationship exists between a user and a partner.
* Handles both directions of a relationship (userId => partnerId or partnerId => userId).
* Optionally checks relationship status.
*/
export const ensureRelationshipExists = async (userId: number, partnerId: number, status?: 'pending' | 'accepted') => {
try { try {
let message = 1; // Ensure bidirectional relationship (user1 <-> user2 or user2 <-> user1)
if (userId === 1) const relationship = await db.select({
message = 2; relationshipId: schema.userRelationships.relationshipId,
const result = await db.select({ status: schema.relationships.status,
receivedMessage: schema.users.message, })
}).from(schema.users) .from(schema.userRelationships)
.where(eq(schema.users.id, message)) .leftJoin(
return result; schema.relationships,
eq(schema.userRelationships.relationshipId, schema.relationships.id)
)
.where(or(
and(
eq(schema.userRelationships.userId, userId),
eq(schema.userRelationships.userId, partnerId) // Check if userId -> partnerId
),
and(
eq(schema.userRelationships.userId, partnerId),
eq(schema.userRelationships.userId, userId) // Check if partnerId -> userId (reverse relationship)
)
));
if (!relationship.length) {
throw new Error('Relationship does not exist');
}
if (status && relationship[0]?.status !== status) {
throw new Error(`Relationship is not in ${status} status`);
}
return relationship[0];
} catch (error) { } catch (error) {
console.error("Error fetching message", error); if (error instanceof Error) {
throw new Error("Failed to fetch message"); throw new Error(`Error checking relationship: ${error.message}`);
} else {
throw new Error("Unknown error occurred while checking relationship");
}
} }
}; };
export const setMessage = async (userId: number, message: string) => { // --- User Management Functions --- //
export const getUserById = async (userId: number) => {
try {
return await ensureUserExists(userId);
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch user by id: ${error.message}`);
} else {
throw new Error("Unknown error occurred while fetching user by id");
}
}
};
export const getUserByUsername = async (username: string) => {
try {
const user = await db.select().from(schema.users)
.where(eq(schema.users.username, username));
if (user.length === 0) throw new Error('User not found');
return user[0];
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch user by username: ${error.message}`);
} else {
throw new Error("Unknown error occurred while fetching user by username");
}
}
};
export const createUser = async (
username: string, email: string, passwordHash: string,
name: string, pfpURL = "", pushToken = ""
) => {
try {
if (!username || !email || !passwordHash || !name) {
throw new Error("Error: All required fields must be filled");
}
// Check if username or email is already taken
const existingUser = await db.select().from(schema.users)
.where(or(eq(schema.users.username, username), eq(schema.users.email, email)));
if (existingUser.length > 0) {
throw new Error("Username or email is already in use");
}
const newUser = await db.insert(schema.users).values({
username, email, passwordHash, name, pfpURL, pushToken,
lastLogin: new Date(),
}).returning();
return newUser;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to create new user: ${error.message}`);
} else {
throw new Error("Unknown error occurred while creating new user");
}
}
};
export const changePassword = async (
userId: number, oldPasswordHash: string, newPasswordHash: string
) => {
try {
// Ensure all arguments are provided
if (!oldPasswordHash || !newPasswordHash) throw new Error("Password fields are required");
const user = await ensureUserExists(userId);
// Validate old password
if (user?.passwordHash !== oldPasswordHash) {
throw new Error("Old password does not match");
}
// Update with the new password hash
await db.update(schema.users)
.set({ passwordHash: newPasswordHash })
.where(eq(schema.users.id, userId));
return { success: true };
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to change password: ${error.message}`);
} else {
throw new Error("Unknown error occurred while changing password");
}
}
};
export const updateUserPFP = async (userId: number, pfpURL: string) => {
try { try {
await db.update(schema.users) await db.update(schema.users)
.set({ message: message }) .set({ pfpURL })
.where(eq(schema.users.id, userId)); .where(eq(schema.users.id, userId));
const otherUserId = userId === 1 ? 2 : 1;
const otherUser = await getUser(otherUserId);
if (otherUser?.[0]?.pushToken) { return { success: true };
await sendPushNotification(otherUser[0].pushToken, message); } catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to update profile: ${error.message}`);
} else { } else {
console.log(`Other user with id ${otherUserId} does not have a push token`); throw new Error("Unknown error occurred while updating profile");
} }
} catch (error) {
console.error("Error setting message", error);
throw new Error("Failed to set message");
}
};
export const getCountdown = async () => {
try {
const result = await db.select({
countdown: schema.countdown.date,
}).from(schema.countdown)
.where(eq(schema.countdown.id, 1))
return result;
} catch (error) {
console.error("Error fetching countdown", error);
throw new Error("Failed to fetch countdown");
}
};
export const setCountdown = async (date: Date) => {
try {
await db.update(schema.countdown)
.set({ date: date })
.where(eq(schema.countdown.id, 1));
} catch (error) {
console.error("Error setting countdown", error);
throw new Error("Failed to set countdown");
} }
}; };
export const updateUserPushToken = async (userId: number, pushToken: string) => { export const updateUserPushToken = async (userId: number, pushToken: string) => {
try { try {
await db.update(schema.users) await db.update(schema.users)
.set({ pushToken: pushToken }) .set({ pushToken })
.where(eq(schema.users.id, userId)); .where(eq(schema.users.id, userId));
console.log(`Push token updated for userId ${userId}`);
return { success: true };
} catch (error) { } catch (error) {
console.error("Error updating push token", error); if (error instanceof Error) {
throw new Error("Failed to update push token"); throw new Error(`Failed to update profile: ${error.message}`);
} else {
throw new Error("Unknown error occurred while updating profile");
}
} }
}; };
export const sendPushNotification = async (expoPushToken: string, message: string) => { export const userLogin = async (username: string, passwordHash: string) => {
const notificationMessage = {
to: expoPushToken,
sound: 'default',
title: `New message!`,
body: message,
data: { message }, // Extra data you might want to send
};
try { try {
await fetch('https://exp.host/--/api/v2/push/send', { const user = await getUserByUsername(username);
method: 'POST', if (user?.passwordHash !== passwordHash) {
headers: { throw new Error("Invalid password");
Accept: 'application/json', }
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/json', // Update last login timestamp
}, await db.update(schema.users)
body: JSON.stringify(notificationMessage), .set({ lastLogin: new Date() })
}); .where(eq(schema.users.id, user.id));
console.log('Push notification sent successfully');
return user;
} catch (error) { } catch (error) {
console.error('Error sending push notification', error); if (error instanceof Error) {
throw new Error(`Failed to Log in: ${error.message}`);
} else {
throw new Error("Unknown error occurred while logging in");
}
}
};
// --- Relationship Management Functions --- //
export const createRelationshipRequest = async (requestorId: number, requestedId: number) => {
try {
await ensureUserExists(requestorId);
await ensureUserExists(requestedId);
// Check if a relationship exists in either direction
const existingRelationship = await db.select()
.from(schema.userRelationships)
.leftJoin(schema.relationships, eq(schema.userRelationships.relationshipId, schema.relationships.id))
.where(or(
and(eq(schema.userRelationships.userId, requestorId), eq(schema.userRelationships.userId, requestedId)), // userId -> requestedId
and(eq(schema.userRelationships.userId, requestedId), eq(schema.userRelationships.userId, requestorId)) // requestedId -> userId
));
if (existingRelationship.length && existingRelationship[0]?.relationship?.status !== 'rejected') {
throw new Error('A relationship already exists or is pending between these users');
}
// Create new relationship entry
const newRelationship = await db.insert(schema.relationships).values({
status: "pending",
}).returning();
// Check for successful insertion
if (!newRelationship.length || !newRelationship[0]?.id)
throw new Error("Failed to create relationship request");
// Add both users to user_relationships table
await db.insert(schema.userRelationships).values([
{ userId: requestorId, relationshipId: newRelationship[0]?.id },
{ userId: requestedId, relationshipId: newRelationship[0].id },
]);
return newRelationship;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to create relationship request: ${error.message}`);
} else {
throw new Error("Unknown error occurred while creating relationship request");
}
}
};
export const updateRelationshipRequest = async (relationshipId: number, status: 'accepted' | 'rejected') => {
try {
const relationship = await db.select().from(schema.relationships)
.where(eq(schema.relationships.id, relationshipId));
if (!relationship.length) {
throw new Error("Relationship request not found");
}
await db.update(schema.relationships)
.set({ status })
.where(eq(schema.relationships.id, relationshipId));
return { success: true };
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to update relationship request: ${error.message}`);
} else {
throw new Error("Unknown error occurred while updating relationship request");
}
}
};
export const deleteRelationship = async (relationshipId: number) => {
try {
await db.transaction(async (trx) => {
await trx.delete(schema.userRelationships)
.where(eq(schema.userRelationships.relationshipId, relationshipId));
await trx.delete(schema.relationships)
.where(eq(schema.relationships.id, relationshipId));
});
return { success: true };
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to delete the relationship: ${error.message}`);
} else {
throw new Error("Unknown error occurred while deleting the relationship");
}
}
};
// --- Message Management Functions --- //
type MediaType = "text" | "image" | "video" | "audio" | "file" | "link";
export const sendMessage = async (
senderId: number, receiverId: number, content: string, mediaUrl?: string, mediaType?: MediaType
) => {
try {
// Ensure both sender and receiver exist
await ensureUserExists(senderId);
await ensureUserExists(receiverId);
// Ensure the relationship exists (no need to use returned 'relationshipId' for now)
await ensureRelationshipExists(senderId, receiverId, "accepted");
// Insert the new message
const message = await db.insert(schema.messages).values({
senderId, receiverId, content, createdAt: new Date(),
}).returning();
// Check for successful insertion of the message
if (!message.length || !message[0]?.id)
throw new Error("Failed to send message");
if (mediaUrl && mediaType) {
await db.insert(schema.messageMedia).values({
messageId: message[0].id,
mediaUrl,
type: mediaType,
});
}
return message;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to send message: ${error.message}`);
} else {
throw new Error("Unknown error occurred while sending message");
}
}
};
export const fetchMessages = async (userId: number, partnerId: number) => {
try {
await ensureUserExists(userId);
await ensureRelationshipExists(userId, partnerId, "accepted");
const messages = await db.select().from(schema.messages)
.where(or(
and(eq(schema.messages.senderId, userId), eq(schema.messages.receiverId, partnerId)),
and(eq(schema.messages.senderId, partnerId), eq(schema.messages.receiverId, userId))
));
return messages;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to retrieve messages: ${error.message}`);
} else {
throw new Error("Unknown error occurred while retrieving messages");
}
}
};
// --- Countdown Management Functions --- //
export const createOrUpdateCountdown = async (relationshipId: number, title: string, date: Date) => {
try {
// Check if countdown already exists
const countdown = await db.select().from(schema.countdowns)
.where(eq(schema.countdowns.relationshipId, relationshipId));
// Update existing or insert countdown
if (countdown.length) {
await db.update(schema.countdowns)
.set({ title, date })
.where(eq(schema.countdowns.relationshipId, relationshipId));
} else {
await db.insert(schema.countdowns).values({
relationshipId, title, date, createdAt: new Date(),
});
}
return { success: true };
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to create/update countdown: ${error.message}`);
} else {
throw new Error("Unknown error occurred while creating/updating countdown");
}
}
};
export const getCountdownByRelationship = async (relationshipId: number) => {
try {
const countdown = await db.select().from(schema.countdowns)
.where(eq(schema.countdowns.relationshipId, relationshipId));
if (!countdown.length) throw new Error("No countdown found");
return countdown[0];
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to retrieve countdown: ${error.message}`);
} else {
throw new Error("Unknown error occurred while retrieving countdown");
}
} }
}; };