Skip to content

Commit

Permalink
Merge pull request #8 from aritra1999/feature/messages
Browse files Browse the repository at this point in the history
feat: messages
  • Loading branch information
aritra1999 authored Dec 9, 2024
2 parents 7ecefbf + f8fc02d commit 707688d
Show file tree
Hide file tree
Showing 57 changed files with 2,170 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

http
12 changes: 12 additions & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
schema: './src/lib/db/schema.ts',
out: './src/lib/db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.SECRET_XATA_PG_ENDPOINT!
},
verbose: true,
strict: true
});
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
"lint": "prettier --check . && eslint .",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
"db:generate": "drizzle-kit --config=drizzle.config.ts generate",
"db:migrate": "drizzle-kit --config=drizzle.config.ts migrate",
"db:studio": "drizzle-kit --config=drizzle.config.ts studio"
},
"devDependencies": {
"@internationalized/date": "^3.6.0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/eslint": "^9.6.0",
"@types/pg": "^8.11.10",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.0.0-next.64",
"bits-ui": "^1.0.0-next.66",
"clsx": "^2.1.1",
"drizzle-kit": "^0.25.0",
"eslint": "^9.7.0",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { userRouter } from './user/user.controller';
import { projectsRouter } from './projects/projects.controller';
import { websitesRouter } from './websites/websites.controller';
import { statusRouter } from './status/status.controller';
import { messagesRouter } from './messages/messages.controller';

export const api = new Hono().basePath('/api');

Expand All @@ -21,6 +22,7 @@ api.route('/user', userRouter);
api.route('/projects', projectsRouter);
api.route('/websites', websitesRouter);
api.route('/status', statusRouter);
api.route('/messages', messagesRouter);

function getAuthConfig(): AuthConfig {
return {
Expand Down
126 changes: 126 additions & 0 deletions src/lib/api/messages/messages.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { verifyAuth } from '@hono/auth-js';
import { Hono, type Context } from 'hono';
import {
createMessage,
deleteMessage,
getMessage,
getMessages,
updateMessage
} from './messages.service';
import { validateRequestBody } from '../middlewares';
import { InsertMessageSchema } from '$lib/db/schema';

export const messagesRouter = new Hono();

export const getMessageController = async (context: Context) => {
const { token } = context.get('authUser');
if (!token) return context.status(401);

const { messageId } = context.req.param();
if (!messageId) return context.json({ error: 'Missing message Id' }, 400);

const messageResponse = await getMessage(String(token.id), messageId);

return context.json(
messageResponse.error ? { error: messageResponse.error } : messageResponse.data,
messageResponse.status
);
};

export const getMessagesController = async (context: Context) => {
const { token } = context.get('authUser');
if (!token) return context.status(401);

const { websiteId } = context.req.query();

const messagesResponse = await getMessages(String(token.id), websiteId);

return context.json(
messagesResponse.error ? { error: messagesResponse.error } : messagesResponse.data,
messagesResponse.status
);
};

export const postMessageController = async (context: Context) => {
const { token } = context.get('authUser');
if (!token) return context.status(401);

const { websiteId } = context.req.param();
if (!websiteId) return context.json({ error: 'Missing website ID' }, 400);

const message = context.get('requestBody');

const createWebsiteResponse = await createMessage({
userId: String(token.id),
websiteId: websiteId,
...message
});

return context.json(
createWebsiteResponse.error
? { error: createWebsiteResponse.error }
: createWebsiteResponse.data,
createWebsiteResponse.status
);
};

export const putMessageController = async (context: Context) => {
const { token } = context.get('authUser');
if (!token) return context.status(401);

const { websiteId } = context.req.param();
if (!websiteId) return context.json({ error: 'Missing website ID' }, 400);

const { messageId } = context.req.param();
if (!messageId) return context.json({ error: 'Missing message ID' }, 400);

const updatedMessage = context.get('requestBody');

const updateWebsiteResponse = await updateMessage(String(token.id), messageId, updatedMessage);

return context.json(
updateWebsiteResponse.error
? { error: updateWebsiteResponse.error }
: updateWebsiteResponse.data,
updateWebsiteResponse.status
);
};

export const deleteMessageController = async (context: Context) => {
const { token } = context.get('authUser');
if (!token) return context.status(401);

const { messageId } = context.req.param();
if (!messageId) return context.json({ error: 'Missing message ID' }, 400);

const deleteMessageResponse = await deleteMessage(String(token.id), messageId);

return context.json(
deleteMessageResponse.error
? { error: deleteMessageResponse.error }
: deleteMessageResponse.data,

deleteMessageResponse.status
);
};

const PartialInsertMessageSchema = InsertMessageSchema.pick({
title: true,
content: true,
startTime: true
});

messagesRouter.use(verifyAuth());
messagesRouter.get('/:messageId', getMessageController);
messagesRouter.get('/', getMessagesController);
messagesRouter.post(
'/:websiteId',
validateRequestBody(PartialInsertMessageSchema),
postMessageController
);
messagesRouter.put(
'/:websiteId/:messageId',
validateRequestBody(PartialInsertMessageSchema),
putMessageController
);
messagesRouter.delete('/:messageId', deleteMessageController);
178 changes: 178 additions & 0 deletions src/lib/api/messages/messages.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { db } from '$lib/db/drizzle';
import { messages, websites, type InsertMessage, type SelectMessagePartial } from '$lib/db/schema';
import { and, count, eq } from 'drizzle-orm';
import type { ServiceResponse } from '../types';
import type { StatusCode } from 'hono/utils/http-status';
import { isUserPro } from '../user/user.service';
import { prettifyErrors } from '$lib/db/utils';

export const canWbsiteHaveMoreMessages = async (
userId: string,
projectId: string
): Promise<boolean> => {
if (await isUserPro(userId)) return true;

return (await getMessagesCount(projectId)) < 5;
};

export const getMessagesCount = async (websiteId: string): Promise<number> => {
return await db
.select({ count: count(messages.id) })
.from(messages)
.where(eq(messages.websiteId, websiteId))
.then((response) => response[0].count);
};

export const getMessage = async (
userId: string,
messageId: string
): Promise<ServiceResponse<SelectMessagePartial>> => {
return await db
.select({
id: messages.id,
title: messages.title,
content: messages.content,
startTime: messages.startTime
})
.from(messages)
.where(and(eq(messages.userId, userId), eq(messages.id, messageId)))
.limit(1)
.then((response) => {
if (response.length === 0)
return {
status: 404 as StatusCode,
error: 'Message not found'
};

return {
status: 200 as StatusCode,
data: response[0]
};
})
.catch((error) => {
return {
status: 400 as StatusCode,
error: error.message
};
});
};

export const getMessages = async (
userId: string,
websiteId?: string
): Promise<ServiceResponse<SelectMessagePartial[]>> => {
const condition = websiteId
? and(eq(messages.userId, userId), eq(messages.websiteId, websiteId))
: eq(messages.userId, userId);
return await db
.select({
id: messages.id,
title: messages.title,
content: messages.content,
websiteId: messages.websiteId,
startTime: messages.startTime
})
.from(messages)
.where(condition)
.then((response) => {
return {
status: 200 as StatusCode,
data: response
};
})
.catch((error) => {
return {
status: 400 as StatusCode,
error: error.message
};
});
};

export const createMessage = async (
message: InsertMessage
): Promise<ServiceResponse<SelectMessagePartial>> => {
if (!(await canWbsiteHaveMoreMessages(message.userId, message.websiteId)))
return {
status: 403,
error: 'Hobby users can create 5 messages per website!'
};

return await db
.insert(messages)
.values(message)
.returning({
id: messages.id,
title: messages.title,
content: messages.content,
startTime: messages.startTime
})
.then((response) => {
return {
status: 200 as StatusCode,
data: response[0]
};
})
.catch((error) => {
return {
status: 400 as StatusCode,
error: prettifyErrors(error)
};
});
};

export const updateMessage = async (
userId: string,
messageId: string,
updatedMessage: InsertMessage
): Promise<ServiceResponse<SelectMessagePartial>> => {
return await db
.update(messages)
.set(updatedMessage)
.where(and(eq(websites.userId, userId), eq(messages.id, messageId)))
.returning({
id: messages.id,
title: messages.title,
content: messages.content,
startTime: messages.startTime
})
.then((response) => {
return {
status: 200 as StatusCode,
data: response[0]
};
})
.catch((error) => {
return {
status: 400 as StatusCode,
error: error.message
};
});
};

export const deleteMessage = async (
userId: string,
messageId: string
): Promise<ServiceResponse<{ deletedId: string }>> => {
return await db
.delete(messages)
.where(and(eq(messages.userId, userId), eq(messages.id, messageId)))
.returning({ deletedId: websites.id })
.then((response) => {
if (response.length === 0) {
return {
status: 404 as StatusCode,
error: 'Message not found'
};
}
return {
status: 200 as StatusCode,
data: response[0]
};
})
.catch((error) => {
return {
status: 400 as StatusCode,
error: error.message
};
});
};
Loading

0 comments on commit 707688d

Please sign in to comment.