Database Schema

The database.rules.json file defines the rules for data access within the Firebase Realtime Database. This file is essential for the project, as it ensures data integrity and security.

Firebase Realtime Database

The database is structured as follows:

{
            "activeEventId": "london2024", 
            "admins": {
              "57MrWonzPRNLuDlAw3GHPJ81H4M2": "Todd",
              "6fgpAQCVh0XnENhSWeILL0oPWQy1": "Martine"
            },
            "answers": {
              "exampleEventId": {
                "exampleQuestionId": {
                  "exampleUserId": {
                    "answer": "",
                    "displayName": "Jane Doe",
                    "submitTime": 1701369481152
                  }
                }
              }
            },
            "questions": {
              "text": {
                "qt00": {
                  "questionText": "\"đź’©\" - \"đź’©\"",
                  "answer": "NaN",
                  "used": false
                },
                "qt01": {
                  "questionText": "\"đź’©\".length",
                  "answer": "2",
                  "used": false
                },
                "qt02": {
                  "questionText": "0.1 + 0.2",
                  "answer": "0.30000000000000004",
                  "used": false
                },
                "qt03": {
                  "questionText": "+\"42\"",
                  "answer": "42",
                  "used": false
                },
                "qt05": {
                  "questionText": "NaN == NaN",
                  "answer": "false",
                  "used": false
                },
                "qt06": {
                  "questionText": "3 > 2 > 1",
                  "answer": "false",
                  "used": false
                },
                "qt08": {
                  "questionText": "null instanceof Object",
                  "answer": "false",
                  "used": false
                }
              },
              "choice": {
                "qc00": {
                  "questionText": "1 / 0",
                  "used": false,
                  "answers": {
                    "a": {
                      "answerText": "Infinity",
                      "correct": true
                    },
                    "b": {
                      "answerText": "NaN",
                      "correct": false
                    },
                    "c": {
                      "answerText": "Number.MAX_VALUE",
                      "correct": false
                    },
                    "d": {
                      "answerText": "undefined",
                      "correct": false
                    }
                  }
                },
                "qc01": {
                  "questionText": "[] + []",
                  "used": false,
                  "answers": {
                    "a": {
                      "answerText": "\"\"",
                      "correct": true
                    },
                    "b": {
                      "answerText": "NaN",
                      "correct": false
                    },
                    "c": {
                      "answerText": "undefined",
                      "correct": false
                    },
                    "d": {
                      "answerText": "[]",
                      "correct": false
                    }
                  }
                },
                "qc03": {
                  "questionText": "typeof null",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "\"object\"",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "\"null\"",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "null",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "undefined",
                      "correct": false
                    }
                  }
                },
                "qc04": {
                  "questionText": "parseInt(0.0000005)",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "5",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "undefined",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "0",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "ArgumentError",
                      "correct": false
                    }
                  }
                },
                "qc05": {
                  "questionText": "false == NaN",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "false",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "true",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "undefined",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "NaN",
                      "correct": false
                    }
                  }
                },
                "qc06": {
                  "questionText": "[10, 5, 1].sort()",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "[1, 10, 5]",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "[1, 5, 10]",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "[10, 5, 1]",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "[10, 5, 1]",
                      "correct": false
                    }
                  }
                },
                "qc07": {
                  "questionText": "typeof typeof 1",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "string",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "number",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "false",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "undefined",
                      "correct": false
                    }
                  }
                },
                "qc08": {
                  "questionText": "\"42\" + 1",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "421",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "43",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "\"43\"",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "NaN",
                      "correct": false
                    }
                  }
                },
                "qc09": {
                  "questionText": "\"đź’©\" instanceof String",
                  "used": false,
                  "answers": {
                    "a00": {
                      "answerText": "false",
                      "correct": true
                    },
                    "a01": {
                      "answerText": "true",
                      "correct": false
                    },
                    "a02": {
                      "answerText": "undefined",
                      "correct": false
                    },
                    "a03": {
                      "answerText": "TypeError",
                      "correct": false
                    }
                  }
                },
                "qc02": {
                  "questionText": "[1, 2, 3] + [4, 5, 6]",
                  "used": false,
                  "answers": {
                    "a": {
                      "answerText": "\"1,2,34,5,6\"",
                      "correct": true
                    },
                    "b": {
                      "answerText": "\"1,2,3,4,5,6\"",
                      "correct": false
                    },
                    "c": {
                      "answerText": "[1,2,3,4,5,6]",
                      "correct": false
                    },
                    "d": {
                      "answerText": "\"123456\"",
                      "correct": false
                    }
                  }
                }
              }
            }
          }
          

Data Structure:

  • activeEventId: The ID of the currently active event.
  • admins: A map of user IDs to their names.
  • answers: A nested structure holding answers submitted by users.
    • eventId: The ID of the event.
    • questionId: The ID of the question.
    • userId: The user’s ID.
      • answer: The submitted answer.
      • displayName: The user’s display name.
      • submitTime: The time the answer was submitted.
  • questions: A map of questions with their respective data.
    • type: The type of question (e.g., “text”, “choice”).
    • questionId: The unique ID of the question.
    • questionText: The text of the question.
    • used: Flag indicating if the question has been used in the current event.
    • answer: (For “text” type questions only) The correct answer to the question.
    • answers: (For “choice” type questions only) A map of answer options with their text and correctness.
      • answerId: The ID of the answer option.
      • answerText: The text of the answer option.
      • correct: Boolean indicating whether this answer is correct.

Security Rules

The database.rules.json file controls data access:

{
            "rules": {
              ".read": "root.child('admins').child(auth.uid).val() != null",
              ".write": "root.child('admins').child(auth.uid).val() != null",
              "answers": {
                "$eventId": {
                  "$questionId": {
                    "void": {},
                    "$user_id": {
                      ".write": "!data.exists() && newData.hasChildren(['answer','displayName','submitTime'])",
                      ".validate": "newData.child('submitTime').isNumber() && newData.child('answer').isString() && newData.child('answer').val().length < 100 && newData.child('displayName').val().length < 100"
                    }
                  }
                }
              },
              "activeQuestion": {
                ".read": true
              }
            }
          }
          

Rules:

  • “.read”: Only users listed in the “admins” node can read data.
  • “.write”: Only users listed in the “admins” node can write data.
  • “answers”: Users can only write data to an answer if:
    • The answer does not exist yet.
    • The new data includes the “answer”, “displayName”, and “submitTime” fields.
    • The “submitTime” field is a valid number.
    • The “answer” and “displayName” fields are strings and have a length less than 100 characters.
  • “activeQuestion”: Any user can read the “activeQuestion” node.

Deployment

To deploy changes to the database schema, run the following command:

$ npm run deploy
          

Note: If you modify the rules within the Firebase Console, be sure to copy them to the database.rules.json file. Otherwise, your changes will be overwritten upon deployment.

Example Data

The database.template.json file provides an example of the database structure and data content.

Source:


          ## Top-Level Directory Explanations
          
          <a class='local-link directory-link' data-ref="audience-app/" href="#audience-app/">audience-app/</a> - This directory contains the files for the audience-facing part of the application. It includes HTML files for different pages, static assets like images, and configuration files for Firebase and npm.
          
          <a class='local-link directory-link' data-ref="audience-app/public/" href="#audience-app/public/">audience-app/public/</a> - This subdirectory holds the publicly accessible files of the audience app. It includes HTML files for specific pages, images, and other static assets.
          
          <a class='local-link directory-link' data-ref="presenter-app/" href="#presenter-app/">presenter-app/</a> - This directory contains the files for the presenter-facing part of the application. It includes source code, static assets, and configuration files.
          
          <a class='local-link directory-link' data-ref="presenter-app/build/" href="#presenter-app/build/">presenter-app/build/</a> - This subdirectory holds the compiled and bundled files for the presenter app. It includes HTML, CSS, JavaScript, and image files.
          
          <a class='local-link directory-link' data-ref="presenter-app/src/" href="#presenter-app/src/">presenter-app/src/</a> - This subdirectory contains the source code for the presenter app. It includes components, controllers, routes, styles, and utility functions.
          
          <a class='local-link directory-link' data-ref="presenter-app/tests/" href="#presenter-app/tests/">presenter-app/tests/</a> - This subdirectory contains test files for the presenter app. It includes mocks, controllers, and declarations.