diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml new file mode 100644 index 00000000..027fa95c --- /dev/null +++ b/.github/workflows/firebase-hosting-merge.yml @@ -0,0 +1,20 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on merge +"on": + push: + branches: + - master +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_CODELABZ_GSOC24 }}" + channelId: live + projectId: codelabz-gsoc24 diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml new file mode 100644 index 00000000..4d9726e0 --- /dev/null +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -0,0 +1,17 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on PR +"on": pull_request +jobs: + build_and_preview: + if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_CODELABZ_GSOC24 }}" + projectId: codelabz-gsoc24 diff --git a/database.rules.json b/database.rules.json index f1366029..4335e236 100644 --- a/database.rules.json +++ b/database.rules.json @@ -1,4 +1,5 @@ { + /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ "rules": { ".read": false, ".write": false diff --git a/firestore.rules b/firestore.rules index 8d700cd7..5f1ef816 100644 --- a/firestore.rules +++ b/firestore.rules @@ -1,8 +1,19 @@ rules_version = '2'; + service cloud.firestore { match /databases/{database}/documents { + + // This rule allows anyone with your Firestore database reference to view, edit, + // and delete all data in your Firestore database. It is useful for getting + // started, but it is configured to expire after 30 days because it + // leaves your app open to attackers. At that time, all client + // requests to your Firestore database will be denied. + // + // Make sure to write security rules for your app before that time, or else + // all client requests to your Firestore database will be denied until you Update + // your rules match /{document=**} { - allow read, write: if true; + allow read, write: if request.time < timestamp.date(2024, 3, 4); } } } \ No newline at end of file diff --git a/functions/.eslintrc.js b/functions/.eslintrc.js new file mode 100644 index 00000000..65f0d8b1 --- /dev/null +++ b/functions/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + env: { + es6: true, + node: true + }, + parserOptions: { + ecmaVersion: 2018 + }, + extends: ["eslint:recommended", "google"], + rules: { + "no-restricted-globals": ["error", "name", "length"], + "prefer-arrow-callback": "error", + quotes: ["error", "double", { allowTemplateLiterals: true }] + }, + overrides: [ + { + files: ["**/*.spec.*"], + env: { + mocha: true + }, + rules: {} + } + ], + globals: {} +}; diff --git a/functions/.gitignore b/functions/.gitignore index 70197f08..40b878db 100644 --- a/functions/.gitignore +++ b/functions/.gitignore @@ -1,2 +1 @@ -node_modules/ -private/ +node_modules/ \ No newline at end of file diff --git a/functions/index.js b/functions/index.js index 8e98586a..6fa37be1 100644 --- a/functions/index.js +++ b/functions/index.js @@ -1,50 +1,19 @@ -const functions = require("firebase-functions"); -const dotenv = require("dotenv"); -dotenv.config({ - path: "../.env" -}); - /** - * +++++++++++++++++++CLOUD FUNCTIONS+++++++++++++++++++++++++++++ - */ - -/**Import functions + * Import function triggers from their respective submodules: + * + * const {onCall} = require("firebase-functions/v2/https"); + * const {onDocumentWritten} = require("firebase-functions/v2/firestore"); + * + * See a full list of supported triggers at https://firebase.google.com/docs/functions */ -const onCallFunctions = require("./cloud_functions/onCallFunctions"); -const onCreateFunctions = require("./cloud_functions/onCreateFunctions"); -const onWriteFunctions = require("./cloud_functions/onWriteFunctions"); -const onUpdateFunctions = require("./cloud_functions/onUpdateFunctions"); -const pubSubFunctions = require("./cloud_functions/pubSubFunctions"); - -//+++++++++++++++++++++onCall Functions+++++++++++++++++++++++++++++++++ -exports.resendVerificationEmail = functions.https.onCall( - onCallFunctions.resendVerificationEmailHandler -); - -exports.sendPasswordUpdateEmail = functions.https.onCall( - onCallFunctions.sendPasswordUpdateEmailHandler -); - -//+++++++++++++++++++++onCreate Functions+++++++++++++++++++++++++++++++ -exports.sendVerificationEmail = functions.auth - .user() - .onCreate(onCreateFunctions.sendVerificationEmailHandler); - -exports.createOrganization = functions.firestore - .document("cl_org_general/{org_handle}") - .onCreate(onCreateFunctions.createOrganizationHandler); -//++++++++++++++++++++onWrite Functions+++++++++++++++++++++++++++++++ -exports.registerUserHandle = functions.firestore - .document("cl_user/{uid}") - .onWrite(onWriteFunctions.registerUserHandleHandler); +const { onRequest } = require("firebase-functions/v2/https"); +const logger = require("firebase-functions/logger"); -//++++++++++++++++++++onUpdate Functions++++++++++++++++++++++++++++++ -exports.updateOrgUser = functions.firestore - .document("cl_org_general/{org_handle}/cl_org_users/users") - .onUpdate(onUpdateFunctions.addOrgUserHandler); +// Create and deploy your first functions +// https://firebase.google.com/docs/functions/get-started -//++++++++++++++++++++Pub/Sub Functions++++++++++++++++++++++++++++++ -exports.deleteTutorialSteps = functions.pubsub - .schedule("every 7 days") - .onRun(pubSubFunctions.deleteTutorialStepsHandler); +// exports.helloWorld = onRequest((request, response) => { +// logger.info("Hello logs!", {structuredData: true}); +// response.send("Hello from Firebase!"); +// }); diff --git a/functions/package.json b/functions/package.json index f20d6f03..a88bc5e2 100644 --- a/functions/package.json +++ b/functions/package.json @@ -10,18 +10,17 @@ "logs": "firebase functions:log" }, "engines": { - "node": "10" + "node": "18" }, + "main": "index.js", "dependencies": { - "dotenv": "^16.0.1", - "firebase-admin": "^9.0.0", - "firebase-functions": "^3.9.0", - "lodash": "^4.17.19" + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" }, "devDependencies": { - "eslint": "^5.12.0", - "eslint-plugin-promise": "^4.0.1", - "firebase-functions-test": "^0.2.0" + "eslint": "^8.15.0", + "eslint-config-google": "^0.14.0", + "firebase-functions-test": "^3.1.0" }, "private": true } diff --git a/functions/private/cl-dev-pk.json b/functions/private/cl-dev-pk.json new file mode 100644 index 00000000..f4009e09 --- /dev/null +++ b/functions/private/cl-dev-pk.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "codelabz-gsoc24", + "private_key_id": "f6aec9fe78ebf53b5f6086cdbd09e245f6c0cd22", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCexj7GEzOAbBBJ\nSamCs16P0+CZuzoOir3O1a4GCE2/dGwf7E6YNKzvSqJYexS0FNJwjol16vVx0jUt\n+NJS9kpkTPf3ds4p++5DAy46rom61ktMYz/YZvWTnkke+0+oR50+L8VZrtn8rW2m\n5vhX8uZcx/Mj1+sVC4xMQPPrUwFDTHEkahKkYsnhSmStwHsOGG/KaeKttnTUN0OX\nJSwCtIfHWiKc2eWskgoYeI6V4JHhgp7/G3DJjZAtTMbS8dyXlYuTGMb8upkYSZu5\n8i7sSQcOKmUXU3/ddnlwYX7nmg1NhOmBg0pUSrS4jmagqGjis9N2TrBjzDX1U2I1\ntWQBhICHAgMBAAECggEAAoVJarq3wNG9fOVCfB2aN5uqZn9J/gA4izQUzBSb49I6\nPFPzopYTf+HtOeJhdN/FFbqYPdsBEoppkiqab8JL5AykxO3fQc4K/WuWLEzEno3X\nf76DoPfhe4ix/5jOrgzN5WERN61/Z5Bs2FZftoItI2eAWZDhwTbjf1CjVdGFwWNK\n3dlMlVauDvbMKZABan8bjsLTVAox95hXVuqWrqVtheHwpT+hmXgcmdeOx9DlSPSs\nDHEYnlh6O6gxY9RiQ7LAjws68eO9GxCZDkfvM3Ok/g6SIswrgWL2yTBq8SPKDcwM\nNs2Aulb9jnYsC324u5vBS1UXw6Y0xdyn2pxwtGrqoQKBgQDbI33z0u18lA2ZPx6G\nPqHUdW62i+vbCHLWYB6fCha7t39ZhieVpuVvfcN2XmLiPmFUJqMbxuSEn7qK3DcP\n+g/wEAvbouCm2oB+LQkE4WR7j1Uam+hQYN7s2yxXcqiX4VW3wgIpYY1XNnHqZkup\n8yGSu0mfXR67uGMvdBLVIFRz1QKBgQC5e1vwWXVXSTMO7Ml3T7ea30aM+LyMA6Bi\nBlNcqRaXvcB3DalNBHzabKFB7P2UR8ZH1nBOliEVeKJhP4+itlBl0GqVq1EP88VC\nIdPNreWjdoUyFlqnLOu1xaEOJAEve4YS+V5AJzCmozQJNvmdeVkOitzv0XvV+Flp\nuUBqlNd86wKBgDmJJQ26iL4XxUZCK0qF8UluF8Z4EFHu8u/URtXs+TEKKbagoY4K\nRt0yAPr4JzBNvpIwnsyxONiVc4336cEZH8wg+mwNZLyKTAhU3LRaVV6XsHmPC7zm\n4kD//rFrGlbeQ/o+RwEEau7GDbzEZQNXIa573AWqlmIlNG2GJVet6F6NAoGBAKXv\nDDMbdPRfkgP6JcpNUM6GjNE0/UitPeA0FIPC6Wla4kIfwKQcLa4inKkj4T+0blh6\nKQLFIFfbEjm56UABpi9Pouq+1shUptYg+SD6P4RbVZGXmgYRE9YMNac24rCd6zYy\nTPVLmiSZwMW1nt4YX2m5JSqO2CB2C1ef2VcATT99AoGAT/nNCZN8C+gu1gJ6mkmq\nJDgNdLV907vKenZlOez5EB6Uns20v1YyOuK7EyMUnf9NHr/5ZyPw+yT7RCJ28YO3\nclXF4gkKuVwcxm/ccsZhmqBb2EzrF1rnbcgtEocRG3zYmPhFZdqeHLQ9/GSneC8Q\neAxgoA/wdxA/piq5GTpiFKg=\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-45w7y@codelabz-gsoc24.iam.gserviceaccount.com", + "client_id": "112002644236744622520", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-45w7y%40codelabz-gsoc24.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/public/index.html b/public/index.html index 75393131..db385020 100644 --- a/public/index.html +++ b/public/index.html @@ -1,41 +1,89 @@ - + - - - - - + + + Welcome to Firebase Hosting - - + + + + + + + + + + + + - - - CodeLabz + + + - -
- +
+

Welcome

+

Firebase Hosting Setup Complete

+

You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!

+ Open Hosting Documentation +
+

Firebase SDK Loading…

+ + diff --git a/src/components/Card/CardWithoutPicture.jsx b/src/components/Card/CardWithoutPicture.jsx index abc4ecb0..4de144c0 100644 --- a/src/components/Card/CardWithoutPicture.jsx +++ b/src/components/Card/CardWithoutPicture.jsx @@ -72,6 +72,8 @@ export default function CardWithoutPicture({ tutorial }) { const dispatch = useDispatch(); const firebase = useFirebase(); const firestore = useFirestore(); + const [toggleTitle, setToggleTitle] = useState(false); + const [toggleSummary, setToggleSummary] = useState(false); const handleIncrement = () => { setCount(count + 1); }; @@ -147,7 +149,37 @@ export default function CardWithoutPicture({ tutorial }) { data-testId="codelabzDetails" > - {tutorial?.title} + {tutorial?.title.length > 200 + ? toggleTitle + ? tutorial?.title + : tutorial?.title.slice(0, 200) + : tutorial?.title} + {tutorial?.title.length > 200 ? ( + toggleTitle ? ( + "" + ) : ( + <> + .... + + + ) + ) : ( + "" + )} - {tutorial?.summary} + {tutorial?.summary.length > 600 + ? toggleSummary + ? tutorial?.summary + : tutorial?.summary.slice(0, 600) + : tutorial?.summary} + {tutorial?.summary.length > 600 ? ( + toggleSummary ? ( + "" + ) : ( + <> + .... + + + ) + ) : ( + "" + )} + {/* {tutorial?.summary} */} diff --git a/src/components/Tutorials/MyTutorials/BaseTutorialsComponent/TutorialCard.jsx b/src/components/Tutorials/MyTutorials/BaseTutorialsComponent/TutorialCard.jsx index 6ba03913..3dc77e23 100644 --- a/src/components/Tutorials/MyTutorials/BaseTutorialsComponent/TutorialCard.jsx +++ b/src/components/Tutorials/MyTutorials/BaseTutorialsComponent/TutorialCard.jsx @@ -26,12 +26,16 @@ const TutorialCard = ({ title="Tutorial icon" /> - + - {title} + {title.length > 25 ? title.slice(0, 25) : title} + {title.length > 25 ? "..." : ""} - {title} + {title.length > 180 ? title.slice(0, 180) : title} + {title.length > 180 ? "..." : ""} {loading ? : null} {loading ? : null} @@ -44,16 +48,16 @@ const TutorialCard = ({ p={1} m={1} bgcolor="background.paper" - sx={{ height: 100 }} + sx={{ height: 70, ml: 3 }} > - + diff --git a/src/store/actions/tutorialsActions.js b/src/store/actions/tutorialsActions.js index 7d273c1d..f6adedc4 100644 --- a/src/store/actions/tutorialsActions.js +++ b/src/store/actions/tutorialsActions.js @@ -230,36 +230,36 @@ export const getCurrentTutorialData = export const addNewTutorialStep = ({ owner, tutorial_id, title, time, id }) => - async (firebase, firestore, dispatch) => { - try { - dispatch({ type: actions.CREATE_TUTORIAL_STEP_START }); + async (firebase, firestore, dispatch) => { + try { + dispatch({ type: actions.CREATE_TUTORIAL_STEP_START }); - await firestore - .collection("tutorials") - .doc(tutorial_id) - .collection("steps") - .doc(id) - .set({ - content: `Switch to editor mode to begin ${title} step`, - id, - time, - title, - visibility: true, - deleted: false - }); + await firestore + .collection("tutorials") + .doc(tutorial_id) + .collection("steps") + .doc(id) + .set({ + content: `Switch to editor mode to begin ${title} step`, + id, + time, + title, + visibility: true, + deleted: false + }); - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); - dispatch({ type: actions.CREATE_TUTORIAL_STEP_SUCCESS }); - } catch (e) { - console.log("CREATE_TUTORIAL_STEP_FAIL", e.message); - dispatch({ type: actions.CREATE_TUTORIAL_STEP_FAIL, payload: e.message }); - } - }; + dispatch({ type: actions.CREATE_TUTORIAL_STEP_SUCCESS }); + } catch (e) { + console.log("CREATE_TUTORIAL_STEP_FAIL", e.message); + dispatch({ type: actions.CREATE_TUTORIAL_STEP_FAIL, payload: e.message }); + } + }; export const clearCreateTutorials = () => dispatch => dispatch({ type: actions.CLEAR_CREATE_TUTORIALS_STATE }); @@ -305,78 +305,78 @@ export const setCurrentStepContent = export const hideUnHideStep = (owner, tutorial_id, step_id, visibility) => - async (firebase, firestore, dispatch) => { - try { - /* not being used */ - // const type = await checkUserOrOrgHandle(owner)(firebase); - await firestore - .collection("tutorials") - .doc(tutorial_id) - .collection("steps") - .doc(step_id) - .update({ - [`visibility`]: !visibility, - updatedAt: firestore.FieldValue.serverTimestamp() - }); + async (firebase, firestore, dispatch) => { + try { + /* not being used */ + // const type = await checkUserOrOrgHandle(owner)(firebase); + await firestore + .collection("tutorials") + .doc(tutorial_id) + .collection("steps") + .doc(step_id) + .update({ + [`visibility`]: !visibility, + updatedAt: firestore.FieldValue.serverTimestamp() + }); - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); - } catch (e) { - console.log(e.message); - } - }; + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); + } catch (e) { + console.log(e.message); + } + }; export const publishUnpublishTutorial = (owner, tutorial_id, isPublished) => - async (firebase, firestore, dispatch) => { - try { - await firestore - .collection("tutorials") - .doc(tutorial_id) - .update({ - ["isPublished"]: !isPublished - }); + async (firebase, firestore, dispatch) => { + try { + await firestore + .collection("tutorials") + .doc(tutorial_id) + .update({ + ["isPublished"]: !isPublished + }); - getCurrentTutorialData(owner, tutorial_id)(firebase, firestore, dispatch); - } catch (e) { - console.log(e.message); - } - }; + getCurrentTutorialData(owner, tutorial_id)(firebase, firestore, dispatch); + } catch (e) { + console.log(e.message); + } + }; export const removeStep = (owner, tutorial_id, step_id, current_step_no) => - async (firebase, firestore, dispatch) => { - try { - await firestore - .collection("tutorials") - .doc(tutorial_id) - .collection("steps") - .doc(step_id) - .delete() - - // const data = await firestore - // .collection("tutorials") - // .doc(tutorial_id) - // .collection("steps") - // .doc(step_id) - // .get(); - - await setCurrentStepNo( - current_step_no > 0 ? current_step_no - 1 : current_step_no - )(dispatch); - - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); - } catch (e) { - console.log(e.message); - } - }; + async (firebase, firestore, dispatch) => { + try { + await firestore + .collection("tutorials") + .doc(tutorial_id) + .collection("steps") + .doc(step_id) + .delete(); + + // const data = await firestore + // .collection("tutorials") + // .doc(tutorial_id) + // .collection("steps") + // .doc(step_id) + // .get(); + + await setCurrentStepNo( + current_step_no > 0 ? current_step_no - 1 : current_step_no + )(dispatch); + + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); + } catch (e) { + console.log(e.message); + } + }; export const setCurrentStep = data => async dispatch => dispatch({ type: actions.SET_EDITOR_DATA, payload: data }); @@ -465,69 +465,69 @@ export const remoteTutorialImages = export const updateStepTitle = (owner, tutorial_id, step_id, step_title) => - async (firebase, firestore, dispatch) => { - try { - const dbPath = `tutorials/${tutorial_id}/steps`; - await firestore - .collection(dbPath) - .doc(step_id) - .update({ - [`title`]: step_title, - updatedAt: firestore.FieldValue.serverTimestamp() - }); + async (firebase, firestore, dispatch) => { + try { + const dbPath = `tutorials/${tutorial_id}/steps`; + await firestore + .collection(dbPath) + .doc(step_id) + .update({ + [`title`]: step_title, + updatedAt: firestore.FieldValue.serverTimestamp() + }); - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); - } catch (e) { - console.log(e); - } - }; + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); + } catch (e) { + console.log(e); + } + }; export const updateStepTime = (owner, tutorial_id, step_id, step_time) => - async (firebase, firestore, dispatch) => { - try { - const dbPath = `tutorials/${tutorial_id}/steps`; - - await firestore - .collection(dbPath) - .doc(step_id) - .update({ - [`time`]: step_time, - updatedAt: firestore.FieldValue.serverTimestamp() - }); + async (firebase, firestore, dispatch) => { + try { + const dbPath = `tutorials/${tutorial_id}/steps`; - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); - } catch (e) { - console.log(e.message); - } - }; + await firestore + .collection(dbPath) + .doc(step_id) + .update({ + [`time`]: step_time, + updatedAt: firestore.FieldValue.serverTimestamp() + }); + + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); + } catch (e) { + console.log(e.message); + } + }; export const setTutorialTheme = ({ tutorial_id, owner, bgColor, textColor }) => - async (firebase, firestore, dispatch) => { - try { - const dbPath = `tutorials`; + async (firebase, firestore, dispatch) => { + try { + const dbPath = `tutorials`; - await firestore.collection(dbPath).doc(tutorial_id).update({ - text_color: textColor, - background_color: bgColor, - updatedAt: firestore.FieldValue.serverTimestamp() - }); + await firestore.collection(dbPath).doc(tutorial_id).update({ + text_color: textColor, + background_color: bgColor, + updatedAt: firestore.FieldValue.serverTimestamp() + }); - await getCurrentTutorialData(owner, tutorial_id)( - firebase, - firestore, - dispatch - ); - } catch (e) { - console.log(e.message); - } - }; + await getCurrentTutorialData(owner, tutorial_id)( + firebase, + firestore, + dispatch + ); + } catch (e) { + console.log(e.message); + } + }; diff --git a/y b/y new file mode 100644 index 00000000..415027e5 --- /dev/null +++ b/y @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +}