delay / flutter_starter

MIT License
422 stars 146 forks source link

When signed in it shows the data of previous user which was signed in before #8

Closed KaivalyaNaik closed 3 years ago

KaivalyaNaik commented 3 years ago
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:simple_gravatar/simple_gravatar.dart';
import 'package:flutter_starter/localizations.dart';
import 'package:flutter_starter/models/models.dart';
import 'package:flutter_starter/ui/auth/auth.dart';
import 'package:flutter_starter/ui/ui.dart';
import 'package:flutter_starter/ui/components/components.dart';

class AuthController extends GetxController {
  static AuthController to = Get.find();
  AppLocalizations_Labels labels;
  TextEditingController nameController = TextEditingController();
  TextEditingController emailController = TextEditingController();
  TextEditingController passwordController = TextEditingController();
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final FirebaseFirestore _db = FirebaseFirestore.instance;
  Rx<User> firebaseUser = Rx<User>();
  Rx<UserModel> firestoreUser = Rx<UserModel>();
  final RxBool admin = false.obs;
  final RxBool teacher =false.obs;
  @override
  void onReady() async {
    //run every time auth state changes
    ever(firebaseUser, handleAuthChanged);
    firebaseUser.value = await getUser;
    firebaseUser.bindStream(user);
    super.onInit();
  }

  @override
  void onClose() {
    nameController?.dispose();
    emailController?.dispose();
    passwordController?.dispose();
    super.onClose();
  }

  handleAuthChanged(_firebaseUser) async {
    //get user data from firestore
    if (_firebaseUser?.uid != null) {
      firestoreUser.bindStream(streamFirestoreUser());
      await isAdmin();
    }

    if (_firebaseUser == null) {
      Get.offAll(SignInUI());
    } else {
      Get.offAll(HomeUI());
    }
  }

  // Firebase user one-time fetch
  Future<User> get getUser async => _auth.currentUser;

  // Firebase user a realtime stream
  Stream<User> get user => _auth.authStateChanges();

  //Streams the firestore user from the firestore collection
  Stream<UserModel> streamFirestoreUser() {
    print('streamFirestoreUser()');
    if (firebaseUser?.value?.uid != null) {
      isAdmin();
      if(admin.value){

        return _db
            .doc('/admin/${firebaseUser.value.uid}')
            .snapshots()
            .map((snapshot) => UserModel.fromMap(snapshot.data()));
      }
      return _db
          .doc('/users/${firebaseUser.value.uid}')
          .snapshots()
          .map((snapshot) => UserModel.fromMap(snapshot.data()));
    }

    return null;
  }

  //get the firestore user from the firestore collection
  Future<UserModel> getFirestoreUser() {

    if (firebaseUser?.value?.uid != null) {
      isAdmin();
      if(admin.value){

        return _db.doc('admin/${firebaseUser.value.uid}').get().then(
            (documentSnapshot) =>UserModel.fromMap(documentSnapshot.data())
        );
      }
      else{
      return _db.doc('/users/${firebaseUser.value.uid}').get().then(
          (documentSnapshot) => UserModel.fromMap(documentSnapshot.data()));
      }
    }
    return null;
  }

  //Method to handle user sign in using email and password
  signInWithEmailAndPassword(BuildContext context) async {
    final labels = AppLocalizations.of(context);
    showLoadingIndicator();
    try {
      await _auth.signInWithEmailAndPassword(
          email: emailController.text.trim(),
          password: passwordController.text.trim());
      emailController.clear();
      passwordController.clear();
      hideLoadingIndicator();
    } catch (error) {
      hideLoadingIndicator();
      Get.snackbar(labels.auth.signInErrorTitle, labels.auth.signInError,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 7),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    }
  }

  // User registration using email and password
  registerWithEmailAndPassword(BuildContext context) async {
    final labels = AppLocalizations.of(context);
    showLoadingIndicator();
    try {
      await _auth
          .createUserWithEmailAndPassword(
              email: emailController.text, password: passwordController.text)
          .then((result) async {
        print('uID: ' + result.user.uid);
        print('email: ' + result.user.email);
        //get photo url from gravatar if user has one
        Gravatar gravatar = Gravatar(emailController.text);
        String gravatarUrl = gravatar.imageUrl(
          size: 200,
          defaultImage: GravatarImage.retro,
          rating: GravatarRating.pg,
          fileExtension: true,
        );
        //create the new user object
        UserModel _newUser = UserModel(
            uid: result.user.uid,
            email: result.user.email,
            name: nameController.text,
            photoUrl: gravatarUrl);
        //create the user in firestore
        _createUserFirestore(_newUser, result.user);
        emailController.clear();
        passwordController.clear();
        hideLoadingIndicator();
      });
    } catch (error) {
      hideLoadingIndicator();
      Get.snackbar(labels.auth.signUpErrorTitle, error.message,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 10),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    }
  }

  //handles updating the user when updating profile
  Future<void> updateUser(BuildContext context, UserModel user, String oldEmail,
      String password) async {
    final labels = AppLocalizations.of(context);
    try {
      showLoadingIndicator();
      await _auth
          .signInWithEmailAndPassword(email: oldEmail, password: password)
          .then((_firebaseUser) {
        _firebaseUser.user
            .updateEmail(user.email)
            .then((value) => _updateUserFirestore(user, _firebaseUser.user));
      });
      hideLoadingIndicator();
      Get.snackbar(labels.auth.updateUserSuccessNoticeTitle,
          labels.auth.updateUserSuccessNotice,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 5),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    } on PlatformException catch (error) {
      //List<String> errors = error.toString().split(',');
      // print("Error: " + errors[1]);
      hideLoadingIndicator();
      print(error.code);
      String authError;
      switch (error.code) {
        case 'ERROR_WRONG_PASSWORD':
          authError = labels.auth.wrongPasswordNotice;
          break;
        default:
          authError = labels.auth.unknownError;
          break;
      }
      Get.snackbar(labels.auth.wrongPasswordNoticeTitle, authError,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 10),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    }
  }

  //updates the firestore user in users collection
  void _updateUserFirestore(UserModel user, User _firebaseUser) {
    _db.doc('/users/${_firebaseUser.uid}').update(user.toJson());
    update();
  }

  //create the firestore user in users collection
  void _createUserFirestore(UserModel user, User _firebaseUser) {
    _db.doc('/users/${_firebaseUser.uid}').set(user.toJson());
    update();
  }

  //password reset email
  Future<void> sendPasswordResetEmail(BuildContext context) async {
    final labels = AppLocalizations.of(context);
    showLoadingIndicator();
    try {
      await _auth.sendPasswordResetEmail(email: emailController.text);
      hideLoadingIndicator();
      Get.snackbar(
          labels.auth.resetPasswordNoticeTitle, labels.auth.resetPasswordNotice,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 5),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    } catch (error) {
      hideLoadingIndicator();
      Get.snackbar(labels.auth.resetPasswordFailed, error.message,
          snackPosition: SnackPosition.BOTTOM,
          duration: Duration(seconds: 10),
          backgroundColor: Get.theme.snackBarTheme.backgroundColor,
          colorText: Get.theme.snackBarTheme.actionTextColor);
    }
  }

  //check if user is an admin user
  isAdmin() async {
    await getUser.then((user) async {
      DocumentSnapshot adminRef =
          await _db.collection('admin').doc(user?.uid).get();
      if (adminRef.exists) {
        admin.value = true;
      } else {
        admin.value = false;
      }
      update();
    });
  }

  isTeacher() async{
    await getUser.then((user) async{
      DocumentSnapshot teachRef =
          await _db.collection('teacher').doc(user?.uid).get();
      if(teachRef.exists){
        teacher.value=true;
      }
      else{
        teacher.value=false;
      }
      update();
    });
  }

  // Sign out
  Future<void> signOut() {
    nameController.clear();
    emailController.clear();
    passwordController.clear();
    _auth.signOut();
    update();
  }
}

This is the AuthController file

import 'package:flutter/material.dart';
import 'package:flutter_starter/localizations.dart';
import 'package:flutter_starter/controllers/controllers.dart';
import 'package:flutter_starter/ui/components/components.dart';
import 'package:flutter_starter/ui/ui.dart';
import 'package:get/get.dart';

class HomeUI extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final labels = AppLocalizations.of(context);

    return GetBuilder<AuthController>(
      init: AuthController(),
      builder: (controller) => controller?.firestoreUser?.value?.uid == null
          ? Center(
              child: CircularProgressIndicator(),
            )
          : Scaffold(
              appBar: AppBar(
                title: Text(labels?.home?.title),
                actions: [
                  IconButton(
                      icon: Icon(Icons.settings),
                      onPressed: () {
                        Get.to(SettingsUI());
                      }),
                ],
              ),
              body: Center(
                child: Column(
                  children: <Widget>[
                    SizedBox(height: 120),
                    Avatar(controller.firestoreUser.value),
                    Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        FormVerticalSpace(),
                        Text(
                            labels.home.uidLabel +
                                ': ' +
                                controller.firestoreUser.value.uid,
                            style: TextStyle(fontSize: 16)),
                        FormVerticalSpace(),
                        Text(
                            labels.home.nameLabel +
                                ': ' +
                                controller.firestoreUser.value.name,
                            style: TextStyle(fontSize: 16)),
                        FormVerticalSpace(),
                        Text(
                            labels.home.emailLabel +
                                ': ' +
                                controller.firestoreUser.value.email,
                            style: TextStyle(fontSize: 16)),
                        FormVerticalSpace(),
                        Text(
                            labels.home.adminUserLabel +
                                ': ' +
                                controller.admin.value.toString(),
                            style: TextStyle(fontSize: 16)),
                      ],
                    ),
                  ],
                ),
              ),
            ),
    );
  }
}

This is the home ui file

delay commented 3 years ago

You need to put in your own firebase credentials and setup your own firebase project. I left my own firebase account info in the web/index.html. You would need to swap that out with your own credentials. The reason I have my own project info is for people to easily check it out and test it without needing to spin up their own project. However if you decided you want to use it for your own project you would obviously want to setup your own database.

delay commented 3 years ago

Or are you saying when you sign in it shows another users info?

KaivalyaNaik commented 3 years ago

Yes I have setup my own firebase project. I have multiple users . Suppose I sign in and signout a user A. Then I try to sign in with user B it shows the information of User A

delay commented 3 years ago

That is not good! I will double check my code.

delay commented 3 years ago

I can't duplicate this problem. When I log out and login with another user everything works fine (using iOS simulator). One thing I noticed in your code is you have an isTeacher(). Is your teacher variable showing true even after logging in with your new user. You didn't add your isTeacher() function to handleAuthChanged function like I did with isAdmin(). I am not sure if that is part of your trouble or not. Maybe try downloading my default project and see if you still get problems and then see where your code and my code differ.

KaivalyaNaik commented 3 years ago

I downloaded your project its working fine . I will check my project for differences and if get in trouble is okay to contact you as i am fairly new to this

delay commented 3 years ago

Yes sure.

KaivalyaNaik commented 3 years ago

Hey

E/flutter (23781): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
E/flutter (23781): #1      _RxImpl.bindStream (package:get/src/state_manager/rx/rx_core/rx_impl.dart:188:47)
E/flutter (23781): #2      AuthController.handleAuthChanged (package:flutter_starter/controllers/auth_controller.dart:54:24)
E/flutter (23781): #3      ever.<anonymous closure> (package:get/src/state_manager/rx/rx_workers/rx_workers.dart:46:42)
E/flutter (23781): #4      _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter (23781): #5      _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (23781): #6      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (23781): #7      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
E/flutter (23781): #8      _DelayedData.perform (dart:async/stream_impl.dart:611:14)
E/flutter (23781): #9      _StreamImplEvents.handleNext (dart:async/stream_impl.dart:730:11)
E/flutter (23781): #10     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:687:7)
E/flutter (23781): #11     _rootRun (dart:async/zone.dart:1182:47)
E/flutter (23781): #12     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (23781): #13     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (23781): #14     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (23781): #15     _rootRun (dart:async/zone.dart:1190:13)
E/flutter (23781): #16     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (23781): #17     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (23781): #18     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (23781): #19     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter (23781): #20     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)```

This is the error I get when signing out a teacher account

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_starter/models/teacher_model.dart'; import 'package:get/get.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:simple_gravatar/simple_gravatar.dart'; import 'package:flutter_starter/localizations.dart'; import 'package:flutter_starter/models/models.dart'; import 'package:flutter_starter/ui/auth/auth.dart'; import 'package:flutter_starter/ui/ui.dart'; import 'package:flutter_starter/ui/components/components.dart';

class AuthController extends GetxController { static AuthController to = Get.find(); AppLocalizations_Labels labels; TextEditingController nameController = TextEditingController(); TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseFirestore _db = FirebaseFirestore.instance; Rx firebaseUser = Rx(); Rx firestoreUser = Rx(); Rx firestoreTeacher =Rx(); final RxBool admin = false.obs; final RxBool teacher=false.obs; @override void onReady() async { //run every time auth state changes ever(firebaseUser, handleAuthChanged); firebaseUser.value = await getUser; firebaseUser.bindStream(user); super.onInit(); }

@override void onClose() { nameController?.dispose(); emailController?.dispose(); passwordController?.dispose(); super.onClose(); }

handleAuthChanged(_firebaseUser) async { //get user data from firestore if (_firebaseUser?.uid != null) { firestoreUser.bindStream(streamFirestoreUser()); await checkAdmin(); await isTeacher(); if(teacher.value){ firestoreTeacher.bindStream(streamFirestoreTeacher()); } } if (_firebaseUser == null) { Get.offAll(SignInUI()); } else { Get.offAll(HomeUI()); } }

// Firebase user one-time fetch Future get getUser async => _auth.currentUser;

// Firebase user a realtime stream Stream get user => _auth.authStateChanges();

//Streams the firestore user from the firestore collection Stream streamFirestoreUser() { print('streamFirestoreUser()'); if (firebaseUser?.value?.uid != null) { return _db .doc('/users/${firebaseUser.value.uid}') .snapshots() .map((snapshot) => UserModel.fromMap(snapshot.data())); }

return null;

}

//get the firestore user from the firestore collection Future getFirestoreUser() { if (firebaseUser?.value?.uid != null) { return _db.doc('/users/${firebaseUser.value.uid}').get().then( (documentSnapshot) => UserModel.fromMap(documentSnapshot.data())); } return null; } Stream streamFirestoreTeacher(){ if(firebaseUser?.value?.uid !=null){ return _db .doc('/teacher/${firebaseUser.value.uid}') .snapshots() .map((snapshot) => TeacherModel.fromMap(snapshot.data())); } return null; } Future getFirestoreTeacher(){ if(firebaseUser?.value?.uid!=null){ return _db.doc('/teacher/${firebaseUser.value.uid}').get().then( (documentSnapshot) => TeacherModel.fromMap(documentSnapshot.data())); } return null; }

//Method to handle user sign in using email and password signInWithEmailAndPassword(BuildContext context) async { final labels = AppLocalizations.of(context); showLoadingIndicator(); try { await _auth.signInWithEmailAndPassword( email: emailController.text.trim(), password: passwordController.text.trim()); emailController.clear(); passwordController.clear(); hideLoadingIndicator(); } catch (error) { hideLoadingIndicator(); Get.snackbar(labels.auth.signInErrorTitle, labels.auth.signInError, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 7), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } }

// User registration using email and password registerWithEmailAndPassword(BuildContext context) async { final labels = AppLocalizations.of(context); showLoadingIndicator(); try { await _auth .createUserWithEmailAndPassword( email: emailController.text, password: passwordController.text) .then((result) async { print('uID: ' + result.user.uid); print('email: ' + result.user.email); //get photo url from gravatar if user has one Gravatar gravatar = Gravatar(emailController.text); String gravatarUrl = gravatar.imageUrl( size: 200, defaultImage: GravatarImage.retro, rating: GravatarRating.pg, fileExtension: true, ); //create the new user object UserModel _newUser = UserModel( uid: result.user.uid, email: result.user.email, name: nameController.text, photoUrl: gravatarUrl); //create the user in firestore _createUserFirestore(_newUser, result.user); emailController.clear(); passwordController.clear(); hideLoadingIndicator(); }); } catch (error) { hideLoadingIndicator(); Get.snackbar(labels.auth.signUpErrorTitle, error.message, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 10), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } }

//handles updating the user when updating profile Future updateUser(BuildContext context, UserModel user, String oldEmail, String password) async { final labels = AppLocalizations.of(context); try { showLoadingIndicator(); await _auth .signInWithEmailAndPassword(email: oldEmail, password: password) .then((_firebaseUser) { _firebaseUser.user .updateEmail(user.email) .then((value) => _updateUserFirestore(user, _firebaseUser.user)); }); hideLoadingIndicator(); Get.snackbar(labels.auth.updateUserSuccessNoticeTitle, labels.auth.updateUserSuccessNotice, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 5), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } on PlatformException catch (error) { //List errors = error.toString().split(','); // print("Error: " + errors[1]); hideLoadingIndicator(); print(error.code); String authError; switch (error.code) { case 'ERROR_WRONG_PASSWORD': authError = labels.auth.wrongPasswordNotice; break; default: authError = labels.auth.unknownError; break; } Get.snackbar(labels.auth.wrongPasswordNoticeTitle, authError, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 10), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } }

//updates the firestore user in users collection void _updateUserFirestore(UserModel user, User _firebaseUser) { _db.doc('/users/${_firebaseUser.uid}').update(user.toJson()); update(); }

//create the firestore user in users collection void _createUserFirestore(UserModel user, User _firebaseUser) { _db.doc('/users/${_firebaseUser.uid}').set(user.toJson()); update(); }

//password reset email Future sendPasswordResetEmail(BuildContext context) async { final labels = AppLocalizations.of(context); showLoadingIndicator(); try { await _auth.sendPasswordResetEmail(email: emailController.text); hideLoadingIndicator(); Get.snackbar( labels.auth.resetPasswordNoticeTitle, labels.auth.resetPasswordNotice, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 5), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } catch (error) { hideLoadingIndicator(); Get.snackbar(labels.auth.resetPasswordFailed, error.message, snackPosition: SnackPosition.BOTTOM, duration: Duration(seconds: 10), backgroundColor: Get.theme.snackBarTheme.backgroundColor, colorText: Get.theme.snackBarTheme.actionTextColor); } }

//check if user is an admin user isAdmin() async { await getUser.then((user) async { DocumentSnapshot adminRef = await _db.collection('admin').doc(user?.uid).get(); if (adminRef.exists) { admin.value = true; } else { admin.value = false; } update(); }); } checkAdmin() async{ User user=_auth.currentUser; if(user!=null){ DocumentSnapshot adminRef =await _db.collection('admin').doc(user?.uid).get(); if(adminRef.exists){ admin.value=true; } else{ admin.value=false; } } update(); } isTeacher() async{ User user=_auth.currentUser; if(user!=null){ DocumentSnapshot teacherRef =await _db.collection('teacher').doc(user?.uid).get(); if(teacherRef.exists) teacher.value=true; else teacher.value=false; } update(); }

// Sign out Future signOut() { nameController.clear(); emailController.clear(); passwordController.clear(); return _auth.signOut(); } }


This is  authController.dart

//Teacher Model class TeacherModel{ String uid; String cls;

TeacherModel({this.uid,this.cls});

factory TeacherModel.fromMap(Map data){ return TeacherModel( uid:data['uid'], cls:data['cls']?? '', ); }

Map<String,dynamic> toJson()=>{"uid":uid,"cls":cls};

}


This is teacher model
delay commented 3 years ago

This is where your error is coming from.

E/flutter (23781): #2 AuthController.handleAuthChanged (package:flutter_starter/controllers/auth_controller.dart:54:24)

But what is the actual error it gave you?

delay commented 3 years ago

Also you seem to be doing this in a different way than I handle it. So you should probably include the cls in your user model. The teacher model only needs the same values as my isAdmin. You are just checking whether your user has teacher permissions. If they do then read the cls property from your users model. This will prevent you from needing to store info in your teacher model and your user model.

delay commented 3 years ago

So basically how I would want to handle it is your user model contains data about all of your types of users. So your teacher has a cls property. So you add this property for each of your teachers. A student would have this property blank. Then you use the isTeacher function to just check whether that users uid is one of your teachers. If it is go ahead and read the cls value from your user model and do whatever you want because you know this is a teacher since they appear in your teacher db. Also you should check out why I did this from the medium article. The isAdmin is in a different table so a person can't make themselves an admin or teacher in your case.

KaivalyaNaik commented 3 years ago

So basically I have to store all info in users model and then according to role read the corresponding property. Also if had to add attendance model how will it go? The students can view their attendance for a particular course and teacher can view the enrolled students. I think i have to write controller for it and according to the role assign permissions But I am not entirely sure how to do it?

delay commented 3 years ago

Well you need to decide how you want to structure your data... and it depends on who would need access and also the ultimate size of the data you might contain. I would recommend going through these tutorial videos as they will help you think about how you can structure it... https://youtu.be/v_hR4K4auoQ

delay commented 3 years ago

Here is a link to the whole set of videos... https://www.youtube.com/playlist?list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ

KaivalyaNaik commented 3 years ago

Thank You!!