Skip to content

Commit

Permalink
Merge pull request #40 from GTBitsOfGood/Keerthi/API
Browse files Browse the repository at this point in the history
Custom event type, custom event, and custom graph type endpoints
  • Loading branch information
SamratSahoo authored Mar 5, 2024
2 parents 9e335aa + 10f955f commit 145fdb0
Show file tree
Hide file tree
Showing 26 changed files with 519 additions and 35 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ We define a couple of different event types that are unified across Bits of Good
"ObjectId": "id of the object (i.e. a specific button, tab, etc.) that was clicked",
"UserId": "id of the user who clicked said event"
}
"Created At": "Date the event was created at"
}
```
- Visit Events: These are events that occur when a user visits a specific page in an application (i.e. a user visits `/app` or `/home`)
Expand All @@ -31,8 +32,8 @@ We define a couple of different event types that are unified across Bits of Good
"Event Properties": {
"PageUrl": "URL of the page that was visited in the app",
"UserId": "user who visited that page",
"Date": "date when they visited a page"
}
},
"Created At": "Date the event was created at"
}
```
- Input Events: These are events where a user inputs a specific piece of information into a input box
Expand All @@ -44,7 +45,8 @@ We define a couple of different event types that are unified across Bits of Good
"ObjectId": "id of the object (i.e. a specific text field) where the user inputted data",
"UserId": "id of the user who inputted the text",
"TextValue": "value of the text that was submitted into the text field"
}
},
"Created At": "Date the event was created at"
}
```

Expand Down
2 changes: 1 addition & 1 deletion api/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ RUN yarn install

RUN chmod -R 777 node_modules/

ENTRYPOINT ["yarn", "dev"]
ENTRYPOINT ["sh", "-c", "rm -rf /tmp/tsx-0 && yarn dev"]
4 changes: 2 additions & 2 deletions api/src/actions/click-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const createClickEvent = async (event: Partial<ClickEvent>) => {
export const getClickEvents = async (date?: Date) => {
await dbConnect();
let fromDate = date ?? new Date(Date.now() - 60 * 60 * 24 * 30 * 1000)
const events = await ClickEventModel.find({ date: { $gte: fromDate } })
const events = await ClickEventModel.find({ createdAt: { $gte: fromDate } })
return events
}

Expand All @@ -22,7 +22,7 @@ export const paginatedGetClickEvents = async (afterDate: Date, afterID: String,
if (project && project._id) {
const events = await ClickEventModel.find(
{
date: { $gte: afterDate },
createdAt: { $gte: afterDate },
...(afterID && { _id: { $gte: afterID } }),
projectId: project._id
})
Expand Down
45 changes: 45 additions & 0 deletions api/src/actions/custom-event-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CustomEventType } from "@/src/utils/types";
import { dbConnect } from "@/src/utils/db-connect";
import CustomEventTypeModel from "@/src/models/custom-event-type";
import CustomEvent from "@/src/models/custom-event";
import CustomGraphType from "@/src/models/custom-graph-type";
import { Types } from "mongoose";


export const findEventForProject = async (projectId: string | Types.ObjectId, category: string, subcategory: string) => {
await dbConnect();
return await CustomEventTypeModel.findOne({ projectId, category, subcategory })

}
export const createCustomEventType = async (eventType: Partial<CustomEventType>) => {
await dbConnect();
const createdEventType = await CustomEventTypeModel.create(eventType);
return createdEventType;
}

export const getCustomEventTypesForProject = async (projectId: string) => {
await dbConnect();
const eventTypes = await CustomEventTypeModel.find({ projectId })
return eventTypes;
}
export const getCustomEventType = async (projectId: string, category: string, subcategory: string) => {
await dbConnect();
const eventType = await CustomEventTypeModel.find({ projectId, category, subcategory })
return eventType;
}
export const getCustomEventTypeID = async (projectId: string, category: string, subcategory: string) => {
await dbConnect();
const eventType = await CustomEventTypeModel.findOne({ projectId, category, subcategory })
return eventType?._id;
}
export const deleteCustomEventType = async (projectId: string, category: string, subcategory: string) => {
const deletedEventType = await CustomEventTypeModel.findOne({ projectId, category, subcategory });
if (!deletedEventType) {
return;
}
let eventTypeId = deletedEventType._id

await CustomEvent.deleteMany({ eventTypeId })
await CustomGraphType.deleteMany({ eventTypeId })
await CustomEventTypeModel.deleteOne({ projectId, category, subcategory })
}
34 changes: 34 additions & 0 deletions api/src/actions/custom-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CustomEventType } from "@/src/utils/types";
import { dbConnect } from "@/src/utils/db-connect";
import CustomEventTypeModel from "@/src/models/custom-event-type";
import CustomEvent from "@/src/models/custom-event";
import CustomGraphType from "@/src/models/custom-graph-type";


export const createCustomEvent = async (projectId: string, eventTypeId: string, properties: object) => {
await dbConnect();
let eventType = await CustomEventTypeModel.findOne({ _id: eventTypeId, projectId })

if (!eventType) {
return null;
}
let typeProperties = eventType.properties;
if (Object.keys(typeProperties).length === Object.keys(properties).length
&& Object.keys(typeProperties).every(k => properties.hasOwnProperty(k))) {
return null;
}
const createdEvent = await CustomEvent.create({ projectId, eventTypeId, properties });
return createdEvent;
}
//one function to get eventTypeId, then this paginated method
export const paginatedGetCustomEvents = async (eventTypeId: string, afterDate: Date, afterID: string, limit: number) => {
await dbConnect();
const events = await CustomEvent.find(
{
createdAt: { $gte: afterDate },
...(afterID && { _id: { $gte: afterID } }),
eventTypeId
})
.limit(limit);
return events
}
35 changes: 35 additions & 0 deletions api/src/actions/custom-graph-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { dbConnect } from "@/src/utils/db-connect";
import CustomGraphTypeModel from "@/src/models/custom-event-type";
import { CustomGraphType, GraphTypes } from "@/src/utils/types";
import CustomEventTypeModel from "@/src/models/custom-event-type";


export const createCustomGraphType = async (newGraph: Partial<CustomGraphType>) => {
await dbConnect();
let eventTypeId = newGraph.eventTypeId
let eventType = await CustomEventTypeModel.findOne({ _id: eventTypeId })
if (!eventType) {
return null;
}
let typeProperties = eventType.properties;
if (!typeProperties.includes(newGraph.xProperty as string) || !typeProperties.includes(newGraph.yProperty as string)) {
return null;
}

if (!Object.values(GraphTypes).includes(newGraph.graphType as GraphTypes)) {
return null;
}
const createdGraphType = await CustomGraphTypeModel.create(newGraph);
return createdGraphType;
}

export const getCustomGraphTypes = async (eventTypeId: string, projectId: string) => {
await dbConnect();
const graphTypes = await CustomGraphTypeModel.find({ eventTypeId, projectId })
return graphTypes
}
export const deleteCustomGraphType = async (_id: string) => {
await dbConnect();
const deletedGraphType = await CustomGraphTypeModel.deleteOne({ _id })
return deletedGraphType
}
4 changes: 2 additions & 2 deletions api/src/actions/input-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const createInputEvent = async (event: Partial<InputEvent>) => {
export const getInputEvents = async (date?: Date) => {
await dbConnect();
const fromDate = date ?? new Date(Date.now() - 60 * 60 * 24 * 30 * 1000)
const events = await InputEventModel.find({ date: { $gte: fromDate } })
const events = await InputEventModel.find({ createdAt: { $gte: fromDate } })
return events
}

Expand All @@ -22,7 +22,7 @@ export const paginatedGetInputEvents = async (afterDate: Date, afterID: String,
if (project && project._id) {
const events = await InputEventModel.find(
{
date: { $gte: afterDate },
createdAt: { $gte: afterDate },
...(afterID && { _id: { $gte: afterID } }),
projectId: project._id
})
Expand Down
5 changes: 5 additions & 0 deletions api/src/actions/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const createProject = async (project: Partial<Project>) => {
return createdProject
}

export const getProjectIDByName = async (projectName: string) => {
await dbConnect();
const project = await ProjectModel.findOne({ projectName });
return project?._id
}
export const getProjectByClientKey = async (clientApiKey: string): Promise<Project | null> => {
await dbConnect();
return await ProjectModel.findOne({ clientApiKey });
Expand Down
4 changes: 2 additions & 2 deletions api/src/actions/visit-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const createVisitEvent = async (event: Partial<VisitEvent>) => {
export const getVisitEvents = async (date?: Date) => {
await dbConnect();
const fromDate = date ?? new Date(Date.now() - 60 * 60 * 24 * 30 * 1000)
const events = await VisitEventModel.find({ date: { $gte: fromDate } })
const events = await VisitEventModel.find({ createdAt: { $gte: fromDate } })
return events
}
export const paginatedGetVisitEvents = async (afterDate: Date, afterID: string, limit: number, projectName: String) => {
Expand All @@ -22,7 +22,7 @@ export const paginatedGetVisitEvents = async (afterDate: Date, afterID: string,

const events = await VisitEventModel.find(
{
date: { $gte: afterDate },
createdAt: { $gte: afterDate },
...(afterID && { _id: { $gte: afterID } }),
projectId: project._id
})
Expand Down
82 changes: 82 additions & 0 deletions api/src/controllers/events/custom-event-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createCustomEventType, getCustomEventTypesForProject, deleteCustomEventType, findEventForProject } from "@/src/actions/custom-event-type";
import { getProjectIDByName } from "@/src/actions/project";
import { getProjectByServerKey } from "@/src/actions/project";
import { relogRequestHandler } from "@/src/middleware/request-middleware";
import APIWrapper from "@/src/utils/api-wrapper";
import { CustomEventType } from "@/src/utils/types";
import { Request } from "express";

const customEventTypeRoute = APIWrapper({
POST: {
config: {
requireClientToken: false,
requireServerToken: true
},
handler: async (req: Request) => {
const { category, subcategory, properties } = req.body;
if (!category || !subcategory) {
throw new Error("You must specify a category and subcategory to create a custom event type!")
}
const project = await getProjectByServerKey(req.headers.servertoken as string);

if (!project) {
throw new Error("Project does not exist for client token")
}
const customEventType: Partial<CustomEventType> = {
category: category,
subcategory: subcategory,
properties: properties,
projectId: project._id,
}

const preexistingEventType = await findEventForProject(project._id, category, subcategory)
if (preexistingEventType != null) {
throw new Error("A custom event type with the same category and subcategory already exists")
}

const createdType = await createCustomEventType(customEventType);
return createdType;
},
},
GET: {
config: {
requireClientToken: false,
requireServerToken: false
},
handler: async (req: Request) => {
const projectName = req.query.projectName
if (!projectName) {
throw new Error("You must specify a project to get custom event types!")
}
const id = await getProjectIDByName(projectName as string);

if (!id) {
throw new Error("Project does not exist")
}
const types = await getCustomEventTypesForProject(id.toString());
return types;
},
},
DELETE: {
config: {
requireClientToken: false,
requireServerToken: true
},
handler: async (req: Request) => {
const { category, subcategory } = req.body
if (!category || !subcategory) {
throw new Error("You must specify a category and subcategory to delete custom event types!")
}
const project = await getProjectByServerKey(req.headers.servertoken as string);

if (!project) {
throw new Error("Project does not exist")
}
await deleteCustomEventType(project._id.toString(), category, subcategory)
},
},

});


export const customEventType = relogRequestHandler(customEventTypeRoute);
69 changes: 69 additions & 0 deletions api/src/controllers/events/custom-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createCustomEvent, paginatedGetCustomEvents } from "@/src/actions/custom-event";
import { getCustomEventTypeID } from "@/src/actions/custom-event-type";
import { getProjectIDByName } from "@/src/actions/project";
import { getProjectByClientKey } from "@/src/actions/project";
import { relogRequestHandler } from "@/src/middleware/request-middleware";
import APIWrapper from "@/src/utils/api-wrapper";
import { CustomEvent } from "@/src/utils/types";
import { Request } from "express";

const customEventRoute = APIWrapper({
POST: {
config: {
requireClientToken: true,
requireServerToken: false
},
handler: async (req: Request) => {
const { eventTypeId, properties } = req.body;
if (!eventTypeId || !properties) {
throw new Error("You must specify a category and subcategory to create a custom event!")
}
const project = await getProjectByClientKey(req.headers.clienttoken as string);

if (!project) {
throw new Error("Project does not exist for client token")
}

const createdEvent = await createCustomEvent(project._id.toString(), eventTypeId, properties);

if (!createdEvent) {
throw new Error("Failed to create custom event");
}
return createdEvent;
},
},
GET: {
config: {
requireClientToken: false,
requireServerToken: false
},
handler: async (req: Request) => {
const { projectName, category, subcategory } = req.query
if (!projectName) {
throw new Error("You must specify a project to get custom event types!")
}
const id = await getProjectIDByName(projectName as string);

if (!id) {
throw new Error("Project does not exist")
}
const eventType = await getCustomEventTypeID(id.toString(), category as string, subcategory as string);
if (!eventType) {
throw new Error("Event type does not exist");
}

const { afterId } = req.query;
const limit = req.query.limit ?? 10
const afterTime = req.query.afterTime ? new Date(req.query.afterTime as string) : new Date(Date.now() - 60 * 60 * 24 * 30 * 1000)
if (!projectName) {
throw new Error("You must specify a project name to create a project!")
}
let events = await paginatedGetCustomEvents(eventType.toString(), afterTime, afterId as string, parseInt(limit as string));
return events;
},
},

});


export const customEvent = relogRequestHandler(customEventRoute);
4 changes: 3 additions & 1 deletion api/src/controllers/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { clickEvent } from '@/src/controllers/events/click-event';
export { inputEvent } from '@/src/controllers/events/input-event';
export { visitEvent } from '@/src/controllers/events/visit-event';
export { visitEvent } from '@/src/controllers/events/visit-event';
export { customEventType } from '@/src/controllers/events/custom-event-type';
export { customEvent} from '@/src/controllers/events/custom-event';
1 change: 0 additions & 1 deletion api/src/controllers/events/visit-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const visitEventRoute = APIWrapper({
eventProperties: {
pageUrl,
userId,
date
}
}

Expand Down
Loading

0 comments on commit 145fdb0

Please sign in to comment.