Skip to content

Commit 0c3fcde

Browse files
authored
Merge pull request #958 from mapswipe/fix/project-draft-topic
Fix project draft topic save
2 parents 7be7d6d + 30a6b57 commit 0c3fcde

File tree

7 files changed

+101
-89
lines changed

7 files changed

+101
-89
lines changed

firebase/database.rules.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
"
3737
},
3838
".indexOn": [
39-
"status", "isFeatured", "teamId"
39+
"status",
40+
"isFeatured",
41+
"teamId",
42+
"projectTopicKey"
4043
]
4144
},
4245
"projectDrafts": {
@@ -138,7 +141,8 @@
138141
},
139142
".indexOn": [
140143
"created",
141-
"teamId"
144+
"teamId",
145+
"usernameKey"
142146
]
143147
},
144148
"OSMAccessToken": {

firebase/functions/src/index.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ admin.initializeApp();
99
// seem possible to split them using the split system for multiple sites from
1010
// https://firebase.google.com/docs/hosting/multisites
1111
import {redirect, token} from './osm_auth';
12+
import { formatProjectTopic, formatUserName } from './utils';
1213

1314
exports.osmAuth = {};
1415

@@ -349,28 +350,28 @@ exports.addProjectTopicKey = functions.https.onRequest(async (_, res) => {
349350
const projectRef = await admin.database().ref('v2/projects').once('value');
350351
const data = projectRef.val();
351352

352-
if (data) {
353+
const isEmptyProject = Object.keys(data).length === 0;
354+
if (isEmptyProject) {
355+
res.status(404).send('No projects found');
356+
}
357+
358+
if (!isEmptyProject && data) {
353359
const newProjectData: {[key: string]: string} = {};
354360

355361
Object.keys(data).forEach((id) => {
356362
if (data[id]?.projectTopic) {
357-
const newProjectTopicKey = data[id].projectTopic?.toLowerCase() as string;
363+
const newProjectTopicKey = formatProjectTopic(data[id].projectTopic);
358364
newProjectData[`v2/projects/${id}/projectTopicKey`] = newProjectTopicKey;
359365
}
360366
});
361-
// NOTE: Update database with the new topic key
362-
await admin.database().ref().update(newProjectData);
363367

364-
// Fetch updated data
365-
const updatedSnapshot = await admin.database().ref('v2/projects').once('value');
366-
const updatedProjectData = updatedSnapshot.val();
367-
368-
res.status(200).send(updatedProjectData);
369-
} else {
370-
res.status(404).send('No projects found');
368+
await admin.database().ref().update(newProjectData);
369+
const updatedProjectsCount = Object.keys(newProjectData).length;
370+
res.status(200).send(`Updated ${updatedProjectsCount} projects.`);
371371
}
372372
} catch (error) {
373-
res.status(500).send('Error fetching projects data');
373+
console.log(error);
374+
res.status(500).send('Some error occurred');
374375
}
375376
});
376377

@@ -379,27 +380,27 @@ exports.addUserNameLowercase = functions.https.onRequest(async (_, res) => {
379380
const userRef = await admin.database().ref('v2/users').once('value');
380381
const data = userRef.val();
381382

382-
if (data) {
383+
const isEmptyUser = Object.keys(data).length === 0;
384+
if (isEmptyUser) {
385+
res.status(404).send('No user found');
386+
}
387+
388+
if (!isEmptyUser && data) {
383389
const newUserData: {[key: string]: string} = {};
384390

385391
Object.keys(data).forEach((id) => {
386392
if (data[id]?.username) {
387-
const newUserNameKey = data[id].username?.toLowerCase() as string;
388-
newUserData[`v2/users/${id}/userNameKey`] = newUserNameKey;
393+
const newUsernameKey = formatUserName(data[id].username);
394+
newUserData[`v2/users/${id}/usernameKey`] = newUsernameKey;
389395
}
390396
});
391-
// NOTE: Update database with the new username lowercase
392-
await admin.database().ref().update(newUserData);
393397

394-
// Fetch updated data
395-
const updatedSnapshot = await admin.database().ref('v2/users').once('value');
396-
const updatedUsersData = updatedSnapshot.val();
397-
398-
res.status(200).send(updatedUsersData);
399-
} else {
400-
res.status(404).send('No user found');
398+
await admin.database().ref().update(newUserData);
399+
const updatedUserCount = Object.keys(newUserData).length;
400+
res.status(200).send(`Updated ${updatedUserCount} users.`);
401401
}
402402
} catch (error) {
403-
res.status(500).send('Error fetching user data');
403+
console.log(error);
404+
res.status(500).send('Some error occurred');
404405
}
405406
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// NOTE: We have a similar function in manager-dashbaord
2+
// manager-dashbaord/app/utills/common.tsx
3+
4+
export const formatProjectTopic = (projectTopic: string) => {
5+
// Note: this will remove start and end space
6+
const projectWithoutStartAndEndSpace = projectTopic.trim();
7+
8+
// Note: this will change multi space to single space
9+
const removeMultiSpaceToSingle = projectWithoutStartAndEndSpace.replace(/\s+/g, ' ');
10+
const newProjectTopic = removeMultiSpaceToSingle.toLowerCase();
11+
12+
return newProjectTopic;
13+
};
14+
15+
// NOTE: this validation mirrors feature from the app on signup
16+
export const formatUserName = (name: string) => {
17+
// Note: remove all space
18+
const removeUserNameSpace = name.replace(/\s+/g, '');
19+
const userNameLowercase = removeUserNameSpace.toLowerCase();
20+
21+
return userNameLowercase;
22+
};

manager-dashboard/app/utils/common.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,16 @@ export const iconMap = listToMap(
308308
(icon) => icon.key,
309309
(icon) => icon.component,
310310
);
311+
312+
// NOTE: We have a similar function in firebase function utils
313+
// firebase/functions/src/utils/index.ts
314+
export const formatProjectTopic = (projectTopic: string) => {
315+
// Note: this will remove start and end space
316+
const projectWithoutStartAndEndSpace = projectTopic.trim();
317+
318+
// Note: this will change multi space to single space
319+
const removeMultiSpaceToSingle = projectWithoutStartAndEndSpace.replace(/\s+/g, ' ');
320+
const newProjectTopic = removeMultiSpaceToSingle.toLowerCase();
321+
322+
return newProjectTopic;
323+
};

manager-dashboard/app/views/NewProject/index.tsx

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
PROJECT_TYPE_FOOTPRINT,
6060
PROJECT_TYPE_COMPLETENESS,
6161
PROJECT_TYPE_CHANGE_DETECTION,
62+
formatProjectTopic,
6263
} from '#utils/common';
6364
import { getValueFromFirebase } from '#utils/firebase';
6465

@@ -278,68 +279,7 @@ function NewProject(props: Props) {
278279
...valuesToCopy
279280
} = finalValues;
280281

281-
try {
282-
const db = getDatabase();
283-
const projectRef = databaseRef(db, 'v2/projects/');
284-
const projectTopicKey = projectTopic?.toLowerCase() as string;
285-
286-
const prevProjectNameQuery = query(
287-
projectRef,
288-
orderByChild('projectTopicKey'),
289-
equalTo(projectTopicKey),
290-
);
291-
292-
const snapshot = await getValueFromFirebase(prevProjectNameQuery);
293-
294-
if (snapshot.exists()) {
295-
setError((prevErr) => ({
296-
...getErrorObject(prevErr),
297-
[nonFieldError]: 'A project with this name already exists, please use a different project name (Please note that the name comparision is not case sensitive)',
298-
projectTopic: 'A project with this name already exists, please use a different project name (Please note that the name comparision is not case sensitive)',
299-
}));
300-
setProjectSubmissionStatus(undefined);
301-
return;
302-
}
303-
304-
const newProjectRef = await pushToDatabase(projectRef);
305-
const newKey = newProjectRef.key;
306-
307-
if (!mountedRef.current) {
308-
return;
309-
}
310-
311-
if (!newKey) {
312-
setError((err) => ({
313-
...getErrorObject(err),
314-
[nonFieldError]: 'Failed to push new key for the project',
315-
}));
316-
setProjectSubmissionStatus('failed');
317-
return;
318-
}
319-
320-
const uploadData = {
321-
...finalValues,
322-
projectTopicKey,
323-
createdAt: (new Date()).getTime(),
324-
};
325-
326-
const putProjectRef = databaseRef(db, `v2/projects/${newKey}`);
327-
await setToDatabase(putProjectRef, uploadData);
328-
} catch (submissionError: unknown) {
329-
if (!mountedRef.current) {
330-
return;
331-
}
332-
// eslint-disable-next-line no-console
333-
console.error(submissionError);
334-
setError((err) => ({
335-
...getErrorObject(err),
336-
[nonFieldError]: 'Some error occurred',
337-
}));
338-
}
339-
340-
const finalFilter = filter === FILTER_OTHERS
341-
? filterText
342-
: filter;
282+
const finalFilter = filter === FILTER_OTHERS ? filterText : filter;
343283

344284
if (valuesToCopy.projectType === PROJECT_TYPE_FOOTPRINT && valuesToCopy.inputType === 'aoi_file') {
345285
const res = await validateAoiOnOhsome(valuesToCopy.geometry, finalFilter);
@@ -388,7 +328,34 @@ function NewProject(props: Props) {
388328
return;
389329
}
390330

331+
// NOTE: All the user don't have permission to access draft project
332+
// FIXME: The firebase rules need to be changed to perform this on draft project
391333
const database = getDatabase();
334+
const projectTopicKeyLowercase = (projectTopic?.trim())?.toLowerCase() as string;
335+
const projectTopicKey = formatProjectTopic(projectTopicKeyLowercase);
336+
const projectRef = databaseRef(database, 'v2/projects/');
337+
338+
const prevProjectNameQuery = query(
339+
projectRef,
340+
orderByChild('projectTopicKey'),
341+
equalTo(projectTopicKey),
342+
);
343+
344+
const snapshot = await getValueFromFirebase(prevProjectNameQuery);
345+
if (!mountedRef.current) {
346+
return;
347+
}
348+
349+
if (snapshot.exists()) {
350+
setError((prevErr) => ({
351+
...getErrorObject(prevErr),
352+
[nonFieldError]: 'A project with this name already exists, please use a different project name (Please note that the name comparison is not case sensitive)',
353+
projectTopic: 'A project with this name already exists',
354+
}));
355+
setProjectSubmissionStatus(undefined);
356+
return;
357+
}
358+
392359
const projectDraftsRef = databaseRef(database, 'v2/projectDrafts/');
393360
const newProjectDraftsRef = await pushToDatabase(projectDraftsRef);
394361
if (!mountedRef.current) {
@@ -402,11 +369,14 @@ function NewProject(props: Props) {
402369

403370
const uploadData = {
404371
...valuesToCopy,
372+
projectTopic,
373+
projectTopicKey,
405374
filter: finalFilter,
406375
image: downloadUrl,
407376
createdBy: userId,
408377
teamId: visibility === 'public' ? null : visibility,
409378
};
379+
410380
await setToDatabase(newProjectRef, uploadData);
411381
if (!mountedRef.current) {
412382
return;

manager-dashboard/app/views/NewProject/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type CustomOptionsForProject = {
5050
}[];
5151

5252
export interface ProjectFormType {
53+
projectTopicKey: string;
5354
projectTopic: string;
5455
projectType: ProjectType;
5556
projectRegion: string;

mapswipe_workers/mapswipe_workers/project_types/project.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self, project_draft):
5454
self.projectNumber = project_draft.get("projectNumber", None)
5555
self.projectRegion = project_draft.get("projectRegion", None)
5656
self.projectTopic = project_draft.get("projectTopic", None)
57+
self.projectTopicKey = project_draft.get("projectTopicKey", None)
5758
self.projectType = int(project_draft["projectType"])
5859
self.requestingOrganisation = project_draft.get("requestingOrganisation", None)
5960
self.requiredResults = 0

0 commit comments

Comments
 (0)