All current db functions are written. Just need to make api routes then we are done
This commit is contained in:
20
src/app/layout.tsx
Normal file
20
src/app/layout.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import "~/styles/globals.css";
|
||||
|
||||
import { GeistSans } from "geist/font/sans";
|
||||
import { type Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create T3 App",
|
||||
description: "Generated by create-t3-app",
|
||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="en" className={`${GeistSans.variable}`}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
37
src/app/page.tsx
Normal file
37
src/app/page.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
|
||||
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]">
|
||||
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/usage/first-steps"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">First Steps →</h3>
|
||||
<div className="text-lg">
|
||||
Just the basics - Everything you need to know to set up your
|
||||
database and authentication.
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">Documentation →</h3>
|
||||
<div className="text-lg">
|
||||
Learn more about Create T3 App, the libraries it uses, and how to
|
||||
deploy it.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
44
src/env.js
Normal file
44
src/env.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
/**
|
||||
* Specify your server-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
DATABASE_URL: z.string().url(),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "test", "production"])
|
||||
.default("development"),
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify your client-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
},
|
||||
|
||||
/**
|
||||
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
||||
* middlewares) or client-side so we need to destruct manually.
|
||||
*/
|
||||
runtimeEnv: {
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
* useful for Docker builds.
|
||||
*/
|
||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||
/**
|
||||
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
|
||||
* `SOME_VAR=''` will throw an error.
|
||||
*/
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
18
src/server/db/index.ts
Normal file
18
src/server/db/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
|
||||
import { env } from "~/env";
|
||||
import * as schema from "./schema";
|
||||
|
||||
/**
|
||||
* Cache the database connection in development. This avoids creating a new connection on every HMR
|
||||
* update.
|
||||
*/
|
||||
const globalForDb = globalThis as unknown as {
|
||||
conn: postgres.Sql | undefined;
|
||||
};
|
||||
|
||||
const conn = globalForDb.conn ?? postgres(env.DATABASE_URL);
|
||||
if (env.NODE_ENV !== "production") globalForDb.conn = conn;
|
||||
|
||||
export const db = drizzle(conn, { schema });
|
163
src/server/db/schema.ts
Normal file
163
src/server/db/schema.ts
Normal file
@ -0,0 +1,163 @@
|
||||
// https://orm.drizzle.team/docs/sql-schema-declaration
|
||||
import { sql } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
serial,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable(
|
||||
'users',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
appleId: varchar('apple_id', { length: 200 }).unique(),
|
||||
email: varchar('email', { length: 100 }).unique().notNull(),
|
||||
fullName: varchar('full_name', { length: 100 }).notNull(),
|
||||
pfpUrl: varchar('pfp_url', { length: 255 }),
|
||||
pushToken: varchar('push_token', { length: 100 }).unique().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
},
|
||||
(table) => ({
|
||||
appleIdIndex: index('apple_id_idx').on(table.appleId),
|
||||
emailIndex: index('email_idx').on(table.email),
|
||||
fullNameIndex: index('full_name_idx').on(table.fullName),
|
||||
})
|
||||
);
|
||||
|
||||
export const relationships = pgTable(
|
||||
'relationships',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
title: varchar('title', { length: 50 })
|
||||
.default("My Relationship").notNull(),
|
||||
requestorId: integer('requestor_id').references(() => users.id).notNull(),
|
||||
isAccepted: boolean('is_accepted').default(false),
|
||||
relationshipStartDate: timestamp('relationship_start_date', { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
requestorIdIndex: index('requestor_id_idx').on(table.requestorId),
|
||||
})
|
||||
);
|
||||
|
||||
export const userRelationships = pgTable(
|
||||
'user_relationships',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').references(() => users.id).notNull(),
|
||||
relationshipId: integer('relationship_id').references(() => relationships.id).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIndex: index('user_id_idx').on(table.userId),
|
||||
relationshipIdIndex: index('relationship_id_idx').on(table.relationshipId),
|
||||
})
|
||||
);
|
||||
|
||||
export const countdowns = pgTable(
|
||||
'countdowns',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
relationshipId: integer('relationship_id').references(() => relationships.id).notNull(),
|
||||
title: varchar('title', { length: 50 })
|
||||
.default('Countdown to Next Visit').notNull(),
|
||||
date: timestamp('date', { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
relationshipIdIndex: index('relationship_id_idx').on(table.relationshipId),
|
||||
})
|
||||
);
|
||||
|
||||
export const messages = pgTable(
|
||||
'messages',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
senderId: integer('sender_id').references(() => users.id).notNull(),
|
||||
receiverId: integer('receiver_id').references(() => users.id).notNull(),
|
||||
text: text('text').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
isRead: boolean('is_read').default(false),
|
||||
hasLocation: boolean('has_location').default(false),
|
||||
hasMedia: boolean('has_media').default(false),
|
||||
hasQuickReply: boolean('has_quick_reply').default(false),
|
||||
},
|
||||
(table) => ({
|
||||
senderIdIndex: index('sender_id_idx').on(table.senderId),
|
||||
receiverIdIndex: index('receiver_id_idx').on(table.receiverId),
|
||||
})
|
||||
);
|
||||
|
||||
export const mediaTypes = pgEnum(
|
||||
'message_media_types',
|
||||
['image', 'video', 'audio', 'file']
|
||||
);
|
||||
export const media = pgTable(
|
||||
'media',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
messageId: integer('message_id').references(() => messages.id).notNull(),
|
||||
type: mediaTypes('type').notNull(),
|
||||
url: varchar('url', { length: 255 }).notNull(),
|
||||
size: numeric('size'),
|
||||
metadata: varchar('metadata', { length: 255 }),
|
||||
order: integer('order'),
|
||||
},
|
||||
(table) => ({
|
||||
messageIdIndex: index('message_id_idx').on(table.messageId),
|
||||
})
|
||||
);
|
||||
|
||||
export const locations = pgTable(
|
||||
'locations',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
messageId: integer('message_id').references(() => messages.id).notNull(),
|
||||
latitude: numeric('latitude').notNull(),
|
||||
longitude: numeric('longitude').notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
messageIdIndex: index('message_id_idx').on(table.messageId),
|
||||
})
|
||||
);
|
||||
|
||||
export const quickReplyType = pgEnum(
|
||||
'quick_reply_types',
|
||||
['radio', 'checkbox']
|
||||
);
|
||||
export const quickReplies = pgTable(
|
||||
'quick_replies',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
messageId: integer('message_id').references(() => messages.id).notNull(),
|
||||
type: quickReplyType('type').notNull(),
|
||||
keepIt: boolean('keep_it').default(false),
|
||||
},
|
||||
(table) => ({
|
||||
messageIdIndex: index('message_id_idx').on(table.messageId),
|
||||
})
|
||||
);
|
||||
|
||||
export const quickReplyOptions = pgTable(
|
||||
'quick_reply_options',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
quickReplyId: integer('quick_reply_id').references(() => quickReplies.id).notNull(),
|
||||
title: varchar('title', { length: 100 }).notNull(),
|
||||
value: varchar('value', { length: 100 }).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
quickReplyIdIndex: index('quick_reply_id_idx').on(table.quickReplyId),
|
||||
})
|
||||
);
|
306
src/server/functions.ts
Normal file
306
src/server/functions.ts
Normal file
@ -0,0 +1,306 @@
|
||||
import 'server-only';
|
||||
import { db } from '~/server/db';
|
||||
import * as schema from '~/server/db/schema';
|
||||
import { eq, and, or, like, not } from 'drizzle-orm';
|
||||
import { User,
|
||||
Relationship,
|
||||
UserRelationship,
|
||||
RelationshipData,
|
||||
Countdown,
|
||||
InitialData,
|
||||
Message,
|
||||
MessageMedia,
|
||||
MessageLocation,
|
||||
QuickReply,
|
||||
QuickReplyOption,
|
||||
} from '~/server/types';
|
||||
|
||||
export const getUser = async (userId: number) => {
|
||||
try {
|
||||
const users = await db.select().from(schema.users)
|
||||
.where(eq(schema.users.id, userId))
|
||||
return (users.length > 0) ? users[0] as User : null;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getInitialDataByAppleId = async (appleId: string) => {
|
||||
try {
|
||||
const users = await db.select().from(schema.users)
|
||||
.where(eq(schema.users.appleId, appleId))
|
||||
if (users.length === 0) return null;
|
||||
const user = users[0] as User;
|
||||
const userRelationships = await db.select()
|
||||
.from(schema.userRelationships)
|
||||
.where(eq(schema.userRelationships.userId, user.id))
|
||||
|
||||
let relationshipData: RelationshipData | undefined;
|
||||
let countdown: Countdown | undefined;
|
||||
|
||||
if (userRelationships.length > 0) {
|
||||
const userRelationship = userRelationships[0] as UserRelationship;
|
||||
const relationships = await db.select()
|
||||
.from(schema.relationships)
|
||||
.where(eq(schema.relationships.id, userRelationship.relationshipId))
|
||||
if (relationships.length > 0) {
|
||||
const relationship = relationships[0] as Relationship;
|
||||
|
||||
const partners = await db.select()
|
||||
.from(schema.users)
|
||||
.innerJoin(schema.userRelationships,
|
||||
eq(schema.users.id, schema.userRelationships.userId))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.userRelationships.relationshipId, relationship.id),
|
||||
not(eq(schema.userRelationships.userId, user.id))
|
||||
)
|
||||
);
|
||||
if (partners.length > 0) {
|
||||
const partner = partners[0]?.users as User;
|
||||
relationshipData = {
|
||||
relationship,
|
||||
partner,
|
||||
};
|
||||
const countdowns = await db.select()
|
||||
.from(schema.countdowns)
|
||||
.where(eq(schema.countdowns.relationshipId, relationship.id))
|
||||
.orderBy(schema.countdowns.date)
|
||||
.limit(1);
|
||||
if (countdowns.length > 0) {
|
||||
countdown = countdowns[0] as Countdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const initialData: InitialData = {
|
||||
user,
|
||||
relationshipData,
|
||||
countdown,
|
||||
};
|
||||
return initialData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
appleId: string, email: string,
|
||||
fullName: string, pushToken: string
|
||||
) => {
|
||||
try {
|
||||
if (!appleId || !email || !fullName || !pushToken) {
|
||||
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.appleId, appleId), eq(schema.users.email, email)));
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
throw new Error("Username or email is already in use");
|
||||
}
|
||||
|
||||
const newUsers: User[] = await db.insert(schema.users).values({
|
||||
appleId, email, fullName, pushToken
|
||||
}).returning() as User[]; // return the newly created user
|
||||
|
||||
if (!newUsers.length || !newUsers[0]?.id)
|
||||
throw new Error("Failed to create new user");
|
||||
|
||||
return newUsers[0];
|
||||
|
||||
} 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 updatePushToken = async (userId: number, pushToken: string): Promise<boolean> => {
|
||||
try {
|
||||
const result = await db.update(schema.users)
|
||||
.set({ pushToken: pushToken })
|
||||
.where(
|
||||
and(
|
||||
eq(schema.users.id, userId),
|
||||
not(eq(schema.users.pushToken, pushToken))
|
||||
)
|
||||
)
|
||||
.returning({ updatedId: schema.users.id });
|
||||
|
||||
return result.length > 0;
|
||||
} catch (error) {
|
||||
console.error('Error updating push token:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePfpUrl = async (userId: number, pfpUrl: string) => {
|
||||
try {
|
||||
const result = await db.update(schema.users)
|
||||
.set({ pfpUrl: pfpUrl })
|
||||
.where(eq(schema.users.id, userId))
|
||||
.returning({ updatedId: schema.users.id });
|
||||
return result.length > 0;
|
||||
} catch (error) {
|
||||
console.error('Error updating pfp url:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const checkRelationshipStatus = async (userId: number): Promise<RelationshipData> => {
|
||||
try {
|
||||
const user = await getUser(userId);
|
||||
if (!user) throw new Error("User not found");
|
||||
const userRelationship = await db.select()
|
||||
.from(schema.userRelationships)
|
||||
.where(eq(schema.userRelationships.userId, user.id))
|
||||
.limit(1)
|
||||
.then(results => results[0]);
|
||||
if (!userRelationship) throw new Error('No relationships found for user');
|
||||
const relationship = await db.select()
|
||||
.from(schema.relationships)
|
||||
.where(eq(schema.relationships.id, userRelationship.relationshipId))
|
||||
.limit(1)
|
||||
.then(results => results[0] as Relationship);
|
||||
if (!relationship) throw new Error('Relationship not found');
|
||||
const partner = await db.select()
|
||||
.from(schema.users)
|
||||
.innerJoin(schema.userRelationships,
|
||||
eq(schema.users.id, schema.userRelationships.userId))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.userRelationships.relationshipId, relationship.id),
|
||||
not(eq(schema.userRelationships.userId, user.id))
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.then(results => results[0]?.users as User);
|
||||
if (!partner) throw new Error('No partner found for relationship');
|
||||
return { relationship, partner };
|
||||
} catch (error) {
|
||||
console.error('Error checking relationship status:', error);
|
||||
throw error; // Re-throw the error to be handled by the caller
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRelationshipStatus = async (
|
||||
userId: number, status: 'accepted' | 'rejected'
|
||||
) => {
|
||||
const users = await db.select().from(schema.users)
|
||||
.where(eq(schema.users.id, userId));
|
||||
const user = users[0] as User;
|
||||
if (!user) throw new Error("User not found");
|
||||
const userRelationships = await db.select()
|
||||
.from(schema.userRelationships)
|
||||
.where(eq(schema.userRelationships.userId, user.id));
|
||||
if (userRelationships.length === 0) {
|
||||
throw new Error('No relationships found for user');
|
||||
}
|
||||
const userRelationship = userRelationships[0] as UserRelationship;
|
||||
const relationships = await db.select()
|
||||
.from(schema.relationships)
|
||||
.where(eq(schema.relationships.id, userRelationship.relationshipId));
|
||||
if (relationships.length === 0) {
|
||||
throw new Error('Relationship not found');
|
||||
}
|
||||
const relationship = relationships[0] as Relationship;
|
||||
if (status === 'accepted') {
|
||||
await db.update(schema.relationships)
|
||||
.set({ isAccepted: true })
|
||||
.where(eq(schema.relationships.id, relationship.id));
|
||||
const partners = await db.select()
|
||||
.from(schema.users)
|
||||
.innerJoin(schema.userRelationships,
|
||||
eq(schema.users.id, schema.userRelationships.userId))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.userRelationships.relationshipId, relationship.id),
|
||||
not(eq(schema.userRelationships.userId, user.id))
|
||||
)
|
||||
);
|
||||
if (partners.length === 0) {
|
||||
throw new Error('No partners found for relationship');
|
||||
}
|
||||
const partner = partners[0]?.users as User;
|
||||
const relationshipData: RelationshipData = {
|
||||
relationship,
|
||||
partner,
|
||||
};
|
||||
return relationshipData;
|
||||
} else if (status === 'rejected') {
|
||||
await db.delete(schema.userRelationships)
|
||||
.where(eq(schema.userRelationships.id, userRelationship.id));
|
||||
await db.delete(schema.relationships)
|
||||
.where(eq(schema.relationships.id, relationship.id));
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const searchUsers = async (userId: number, searchTerm: string) => {
|
||||
try {
|
||||
const users = await db.select().from(schema.users)
|
||||
.where(
|
||||
and(
|
||||
or(
|
||||
like(schema.users.fullName, `%${searchTerm}%`),
|
||||
like(schema.users.email, `%${searchTerm}%`)
|
||||
),
|
||||
not(eq(schema.users.id, userId))
|
||||
)
|
||||
);
|
||||
if (users.length === 0) throw new Error("No users found");
|
||||
return users as User[];
|
||||
} catch (error) {
|
||||
console.error('Error searching users:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createRelationshipRequest = async (userId: number, partnerId: number) => {
|
||||
try {
|
||||
const user = await getUser(userId);
|
||||
if (!user) throw new Error("User not found");
|
||||
const partner = await getUser(partnerId);
|
||||
if (!partner) throw new Error("Partner not found");
|
||||
const existingRelationship = await db.select({
|
||||
relationshipId: schema.userRelationships.relationshipId,
|
||||
status: schema.relationships.isAccepted,
|
||||
})
|
||||
.from(schema.userRelationships)
|
||||
.innerJoin(
|
||||
schema.relationships,
|
||||
eq(schema.userRelationships.relationshipId, schema.relationships.id)
|
||||
)
|
||||
.where(
|
||||
or(
|
||||
eq(schema.userRelationships.userId, user.id),
|
||||
eq(schema.userRelationships.userId, partner.id)
|
||||
)
|
||||
).limit(1);
|
||||
if (existingRelationship.length > 0) {
|
||||
throw new Error("Relationship already exists");
|
||||
}
|
||||
const newRelationship = await db.insert(schema.relationships).values({
|
||||
requestorId: user.id,
|
||||
}).returning() as Relationship[];
|
||||
if (!newRelationship.length || !newRelationship[0]?.id)
|
||||
throw new Error("Failed to create new relationship");
|
||||
const relationship = newRelationship[0];
|
||||
await db.insert(schema.userRelationships).values([
|
||||
{ userId: userId, relationshipId: relationship.id },
|
||||
{ userId: partnerId, relationshipId: relationship.id },
|
||||
]);
|
||||
return { relationship, partner };
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`Failed to create new relationship: ${error.message}`);
|
||||
} else {
|
||||
throw new Error("Unknown error occurred while creating new relationship");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
150
src/server/types.ts
Normal file
150
src/server/types.ts
Normal file
@ -0,0 +1,150 @@
|
||||
/* Types */
|
||||
|
||||
// User Table in DB
|
||||
export type User = {
|
||||
id: number;
|
||||
appleId: string | null;
|
||||
email: string;
|
||||
fullName: string;
|
||||
pfpUrl: string | null;
|
||||
pushToken: string;
|
||||
createdAt: Date;
|
||||
metadata?: Record<string, string>;
|
||||
};
|
||||
// Relationship Table in DB
|
||||
export type Relationship = {
|
||||
id: number;
|
||||
title: string;
|
||||
requestorId: number;
|
||||
isAccepted: boolean;
|
||||
relationshipStartDate: Date;
|
||||
};
|
||||
export type UserRelationship = {
|
||||
id: number;
|
||||
userId: number;
|
||||
relationshipId: number;
|
||||
};
|
||||
// Mutated Data from Relationship
|
||||
// & UserRelationship Tables in DB
|
||||
export type RelationshipData = {
|
||||
relationship: Relationship;
|
||||
partner: User;
|
||||
};
|
||||
// Countdown Table in DB
|
||||
export type Countdown = {
|
||||
id: number;
|
||||
relationshipId: number;
|
||||
title: string;
|
||||
date: Date;
|
||||
createdAt: Date;
|
||||
};
|
||||
// Mutated Data for Login
|
||||
// API Response
|
||||
export type InitialData = {
|
||||
user: User;
|
||||
relationshipData?: RelationshipData;
|
||||
countdown?: Countdown;
|
||||
};
|
||||
// Message Table in DB
|
||||
export type Message = {
|
||||
id: number;
|
||||
senderId: number;
|
||||
receiverId: number;
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
isRead: boolean;
|
||||
hasLocation: boolean;
|
||||
hasMedia: boolean;
|
||||
hasQuickReply: boolean;
|
||||
};
|
||||
// MessageMedia Table in DB
|
||||
export type MessageMedia = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
mediaType:
|
||||
'image' | 'video' | 'audio' | 'file';
|
||||
url: string;
|
||||
size?: number;
|
||||
metadata?: string;
|
||||
order: number;
|
||||
};
|
||||
// MessageLocation Table in DB
|
||||
export type MessageLocation = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
// Quick Reply Table in DB
|
||||
export type QuickReply = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
type: 'radio' | 'checkbox';
|
||||
keepIt: boolean;
|
||||
};
|
||||
// Quick Reply Option Table in DB
|
||||
export type QuickReplyOption = {
|
||||
id: number;
|
||||
quickReplyId: number;
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type GCUser = {
|
||||
_id: number;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
};
|
||||
export type GCQuickReplies = {
|
||||
type: 'radio' | 'checkbox';
|
||||
values: GCQuickReplyOptions[];
|
||||
keepIt?: boolean;
|
||||
};
|
||||
export type GCQuickReplyOptions = {
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
export type GCLocation = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
export type GCMessage = {
|
||||
_id: number;
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
user: GCUser;
|
||||
image?: string;
|
||||
video?: string;
|
||||
audio?: string;
|
||||
location?: GCLocation;
|
||||
system?: boolean;
|
||||
sent?: boolean;
|
||||
received?: boolean;
|
||||
pending?: boolean;
|
||||
quickReplies?: GCQuickReplies;
|
||||
};
|
||||
export type GCState = {
|
||||
messages: any[];
|
||||
step: number;
|
||||
loadEarlier?: boolean;
|
||||
isLoadingEarlier?: boolean;
|
||||
isTyping: boolean;
|
||||
};
|
||||
export enum ActionKind {
|
||||
SEND_MESSAGE = 'SEND_MESSAGE',
|
||||
LOAD_EARLIER_MESSAGES = 'LOAD_EARLIER_MESSAGES',
|
||||
LOAD_EARLIER_START = 'LOAD_EARLIER_START',
|
||||
SET_IS_TYPING = 'SET_IS_TYPING',
|
||||
// LOAD_EARLIER_END = 'LOAD_EARLIER_END',
|
||||
};
|
||||
export type GCStateAction = {
|
||||
type: ActionKind;
|
||||
payload?: any;
|
||||
};
|
||||
export type NotificationMessage = {
|
||||
sound?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: any;
|
||||
};
|
||||
|
3
src/styles/globals.css
Normal file
3
src/styles/globals.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
Reference in New Issue
Block a user