@@ -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
2225import { auth } from 'firebase-admin' ;
@@ -42,7 +45,7 @@ const likesCollection = db.collection('likes');
4245const usersCollection = db . collection ( 'users' ) ;
4346const pendingBuildingsCollection = db . collection ( 'pendingBuildings' ) ;
4447const contactQuestionsCollection = db . collection ( 'contactQuestions' ) ;
45-
48+ const blogPostCollection = db . collection ( 'blogposts' ) ;
4649const 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
72332app . post ( '/api/new-review' , authenticate , async ( req , res ) => {
73333 try {
0 commit comments