Skip to content

Commit 6f508e3

Browse files
Added more routes plus documentation
1 parent bc63f97 commit 6f508e3

File tree

3 files changed

+283
-1
lines changed

3 files changed

+283
-1
lines changed

backend/src/app.ts

Lines changed: 261 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
QuestionForm,
1818
QuestionFormWithId,
1919
LocationTravelTimes,
20+
BlogPost,
21+
BlogPostInternal,
22+
BlogPostWithId,
2023
} from '@common/types/db-types';
2124
// Import Firebase configuration and types
2225
import { auth } from 'firebase-admin';
@@ -42,7 +45,7 @@ const likesCollection = db.collection('likes');
4245
const usersCollection = db.collection('users');
4346
const pendingBuildingsCollection = db.collection('pendingBuildings');
4447
const contactQuestionsCollection = db.collection('contactQuestions');
45-
48+
const blogPostCollection = db.collection('blogposts');
4649
const travelTimesCollection = db.collection('travelTimes');
4750

4851
// Middleware setup
@@ -68,6 +71,263 @@ app.get('/api/faqs', async (_, res) => {
6871
res.status(200).send(JSON.stringify(faqs));
6972
});
7073

74+
/**
75+
* new-blog-post – Creates a new blog post.
76+
*
77+
* @remarks
78+
* This endpoint creates and adds a new blog post into the products database. If necessary data are not given, the endpoint
79+
* will result in an error. Certain fields are defaulted to constant values.
80+
*
81+
* @route POST /api/new-blog-post
82+
*
83+
* @status
84+
* - 201: Successfully created new blog post.
85+
* - 401: Error due to unauthorized access or authentication issues.
86+
*/
87+
app.post('/api/new-blog-post', authenticate, async (req, res) => {
88+
if (!req.user) throw new Error('Not authenticated');
89+
const realUserId = req.user.uid;
90+
try {
91+
const doc = blogPostCollection.doc();
92+
const blogPost = req.body as BlogPost;
93+
if (
94+
blogPost.content === '' ||
95+
blogPost.title === '' ||
96+
!blogPost.photos ||
97+
!blogPost.tags ||
98+
!blogPost.visibility
99+
) {
100+
res.status(401).send('Error: missing fields');
101+
}
102+
doc.set({
103+
...blogPost,
104+
date: new Date(blogPost.date),
105+
likes: 0,
106+
saves: 0,
107+
status: 'PENDING',
108+
userId: realUserId,
109+
});
110+
res.status(201).send(doc.id);
111+
} catch (err) {
112+
console.error(err);
113+
res.status(401).send('Error');
114+
}
115+
});
116+
117+
/**
118+
* delete-blog-post/:blogPostId – Deletes a specified blog post.
119+
*
120+
* @remarks
121+
* This endpoint deletes a specified blog post from its ID. The post is removed from the products database.
122+
* If no blog post is found or the user is not authorized to delete the review, then an error is thrown.
123+
*
124+
* @route PUT /api/delete-blog-post/:blogPostId
125+
*
126+
* @status
127+
* - 200: Successfully deleted the specified blog post.
128+
* - 404: Blog post could not be found from ID.
129+
* - 401: Error due to unauthorized access or authentication issues.
130+
*/
131+
app.put('/api/delete-blog-post/:blogPostId', authenticate, async (req, res) => {
132+
if (!req.user) throw new Error('Not authenticated');
133+
const { blogPostId } = req.params; // Extract the blog post document ID from the request parameters
134+
const { email } = req.user;
135+
// Check if the user is an admin or the creator of the blog post
136+
const blogPostDoc = blogPostCollection.doc(blogPostId);
137+
const blogPostData = (await blogPostDoc.get()).data();
138+
if (!blogPostData) {
139+
res.status(404).send('Blog Post not found');
140+
return;
141+
}
142+
if (!(email && admins.includes(email))) {
143+
res.status(403).send('Unauthorized');
144+
return;
145+
}
146+
try {
147+
// Update the status of the blog post document to 'DELETED'
148+
await blogPostCollection.doc(blogPostId).update({ status: 'ARCHIVED' });
149+
// Send a success response
150+
res.status(200).send('Success');
151+
} catch (err) {
152+
// Handle any errors that may occur during the deletion process
153+
console.log(err);
154+
res.status(401).send('Error');
155+
}
156+
});
157+
158+
/**
159+
* edit-blog-post/:blogPostId – Edits a specified blog post.
160+
*
161+
* @remarks
162+
* This endpoint edits a specified blog post from its ID. The post is edited from the products database.
163+
* If no blog post is found or the user is not authorized to edit the review, then an error is thrown.
164+
*
165+
* @route POST /api/edit-blog-post/:blogPostId
166+
*
167+
* @status
168+
* - 201: Successfully edited the specified blog post.
169+
* - 404: Blog post could not be found from ID.
170+
* - 401: Error due to unauthorized access or authentication issues.
171+
*/
172+
app.post('/api/edit-blog-post/:blogPostId', async (req, res) => {
173+
// if (!req.user) {
174+
// throw new Error('not authenticated');
175+
// }
176+
const { blogPostId } = req.params;
177+
// const { email } = req.user;
178+
try {
179+
const blogPostDoc = blogPostCollection.doc(blogPostId); // specific doc for the id
180+
const blogPostData = (await blogPostDoc.get()).data();
181+
if (!blogPostData) {
182+
res.status(404).send('Blog Post not found');
183+
return;
184+
}
185+
// if (!(email && admins.includes(email))) {
186+
// res.status(401).send('Error: user is not an admin. Not authorized');
187+
// return;
188+
// }
189+
const updatedBlogPost = req.body as BlogPost;
190+
if (updatedBlogPost.content === '' || updatedBlogPost.title === '') {
191+
res.status(401).send('Error: missing fields');
192+
}
193+
blogPostDoc
194+
.update({
195+
...updatedBlogPost,
196+
date: new Date(updatedBlogPost.date),
197+
})
198+
.then(() => {
199+
res.status(201).send(blogPostId);
200+
});
201+
} catch (err) {
202+
console.error(err);
203+
res.status(401).send('Error');
204+
}
205+
});
206+
207+
/**
208+
* blog-post-by-id/:blogPostId – Gets a specified blog post.
209+
*
210+
* @remarks
211+
* This endpoint gets a specified blog post from its ID. The post is grabbed from the products database.
212+
* If no blog post is found, then an error is thrown.
213+
*
214+
* @route GET /api/blog-post-by-id/:blogPostId
215+
*
216+
* @status
217+
* - 200: Successfully edited the specified blog post.
218+
* - 404: Blog post could not be found from ID.
219+
* - 401: Error due to unauthorized access or authentication issues.
220+
*/
221+
app.get('/api/blog-post-by-id/:blogPostId', authenticate, async (req, res) => {
222+
const { blogPostId } = req.params; // Extract the blog post ID from the request parameters
223+
try {
224+
const blogPostDoc = await blogPostCollection.doc(blogPostId).get(); // Get the blog post document from Firestore
225+
if (!blogPostDoc.exists) {
226+
res.status(404).send('Blog Post not found'); // If the document does not exist, return a 404 error
227+
return;
228+
}
229+
const data = blogPostDoc.data();
230+
const blogPost = { ...data, date: data?.date.toDate() } as BlogPostInternal; // Convert the Firestore Timestamp to a Date object
231+
const blogPostWithId = { ...blogPost, id: blogPostDoc.id } as BlogPostWithId; // Add the document ID to the review data
232+
res.status(200).send(JSON.stringify(blogPostWithId)); // Return the review data as a JSON response
233+
} catch (err) {
234+
console.error(err);
235+
res.status(401).send('Error retrieving Blog Post'); // Handle any errors that occur during the process
236+
}
237+
});
238+
239+
/**
240+
* blog-post/like/:userId – Fetches blog posts liked by a user.
241+
*
242+
* @remarks
243+
* This endpoint retrieves blog posts that a user has liked.
244+
*
245+
* @route GET /api/blog-post/like/:userId
246+
*
247+
* @status
248+
* - 200: Successfully retrieved the blog posts.
249+
* - 401: Error due to unauthorized access or authentication issues.
250+
*/
251+
app.get('/api/blog-post/like/:userId', authenticate, async (req, res) => {
252+
if (!req.user) {
253+
throw new Error('not authenticated');
254+
}
255+
const realUserId = req.user.uid;
256+
const { userId } = req.params;
257+
if (userId !== realUserId) {
258+
res.status(401).send("Error: user is not authorized to access another user's likes");
259+
return;
260+
}
261+
const likesDoc = await likesCollection.doc(realUserId).get();
262+
263+
if (likesDoc.exists) {
264+
const data = likesDoc.data();
265+
if (data) {
266+
const blogPostIds = Object.keys(data);
267+
const matchingBlogPosts: BlogPostWithId[] = [];
268+
if (blogPostIds.length > 0) {
269+
const query = blogPostCollection.where(FieldPath.documentId(), 'in', blogPostIds);
270+
const querySnapshot = await query.get();
271+
querySnapshot.forEach((doc) => {
272+
const data = doc.data();
273+
const blogPostData = { ...data, date: data.date.toDate() };
274+
matchingBlogPosts.push({ ...blogPostData, id: doc.id } as BlogPostWithId);
275+
});
276+
}
277+
res.status(200).send(JSON.stringify(matchingBlogPosts));
278+
return;
279+
}
280+
}
281+
282+
res.status(200).send(JSON.stringify([]));
283+
});
284+
285+
/**
286+
* blog-post/like/:userId – Fetches blog posts saved by a user.
287+
*
288+
* @remarks
289+
* This endpoint retrieves blog posts that a user has saved.
290+
*
291+
* @route GET /api/blog-post/save/:userId
292+
*
293+
* @status
294+
* - 200: Successfully retrieved the blog posts.
295+
* - 401: Error due to unauthorized access or authentication issues.
296+
*/
297+
app.get('/api/blog-post/save/:userId', authenticate, async (req, res) => {
298+
if (!req.user) {
299+
throw new Error('not authenticated');
300+
}
301+
const realUserId = req.user.uid;
302+
const { userId } = req.params;
303+
if (userId !== realUserId) {
304+
res.status(401).send("Error: user is not authorized to access another user's saves");
305+
return;
306+
}
307+
const savesDoc = await likesCollection.doc(realUserId).get();
308+
309+
if (savesDoc.exists) {
310+
const data = savesDoc.data();
311+
if (data) {
312+
const blogPostIds = Object.keys(data);
313+
const matchingBlogPosts: BlogPostWithId[] = [];
314+
if (blogPostIds.length > 0) {
315+
const query = blogPostCollection.where(FieldPath.documentId(), 'in', blogPostIds);
316+
const querySnapshot = await query.get();
317+
querySnapshot.forEach((doc) => {
318+
const data = doc.data();
319+
const blogPostData = { ...data, date: data.date.toDate() };
320+
matchingBlogPosts.push({ ...blogPostData, id: doc.id } as BlogPostWithId);
321+
});
322+
}
323+
res.status(200).send(JSON.stringify(matchingBlogPosts));
324+
return;
325+
}
326+
}
327+
328+
res.status(200).send(JSON.stringify([]));
329+
});
330+
71331
// API endpoint to post a new review
72332
app.post('/api/new-review', authenticate, async (req, res) => {
73333
try {

common/types/db-types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,27 @@ export type Review = {
3333
readonly reports?: readonly ReportEntry[];
3434
};
3535

36+
export type BlogPost = {
37+
readonly content: string;
38+
readonly date: Date;
39+
readonly likes?: number;
40+
readonly photos: string[];
41+
readonly status: string;
42+
readonly tags: string[];
43+
readonly title: string;
44+
readonly userId?: string | null;
45+
readonly visibility: string;
46+
readonly saves: number;
47+
};
48+
3649
export type ReviewWithId = Review & Id;
3750

51+
export type BlogPostWithId = BlogPost & Id;
52+
3853
export type ReviewInternal = Review & {};
3954

55+
export type BlogPostInternal = BlogPost & {};
56+
4057
export type Landlord = {
4158
readonly name: string;
4259
readonly contact: string | null;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'; // not strictly required if using the new JSX transform, but fine to keep
2+
3+
export default function BlogPost() {
4+
return null; // temp stub
5+
}

0 commit comments

Comments
 (0)