rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {

        // Common
        function authenticated() { return request.auth.uid != null; }
        function limitOne() { return request.query.limit <= 1; }
        function itsMyDocument(userId) { return request.auth.uid == userId; }

        // Users
        function userNotExists() { return !exists(/databases/$(database)/documents/users/$(request.auth.uid)); }
        function isAnonymous() { return request.auth.token.firebase.sign_in_provider == 'anonymous';}
        function isEmailVerified() { return request.auth.token.email_verified == true; }
        function isPhoneNumberAccount() { return request.auth.token.firebase.sign_in_provider == 'phone' ;}

        // Projects
        function projectData(projectId) { return get(/databases/$(database)/documents/projects/$(projectId)).data; }
        function isAdmin(data) { return data.owner == request.auth.uid || request.auth.uid in data.members;}

        // Org
        function getOrganization(organizationId) { return get(/databases/$(database)/documents/organizations/$(organizationId)).data; }
        function isAnOrgViewer(organization) { return request.auth.uid in organization.viewerUserIds; }
        function isAnOrgEditor(organization) { return request.auth.uid in organization.editorUserIds; }
        function isAnOrgAdmin(organization) { return request.auth.uid in organization.adminUserIds; }
        function isOrgOwner(organization) { return request.auth.uid == organization.ownerUserId; }

        function isSuperAdmin(userId) { return exists(/databases/$(database)/documents/admins/users/admins/$(userId)); }

        match /users/{userId} {
            allow get: if authenticated() && !isAnonymous() || isSuperAdmin(request.auth.uid);
            allow list: if authenticated() && itsMyDocument(userId) && limitOne();
            allow create: if authenticated() && itsMyDocument(userId) && userNotExists();
            allow update: if authenticated() && itsMyDocument(userId);
        }

        // PROJECTS
        match /projects/{projectId} {
            allow get: if true;
            allow list: if authenticated() && (isSuperAdmin(request.auth.uid) || isAdmin(resource.data) || isAnOrgViewer(getOrganization(resource.data.organizationId)) );
            allow create: if authenticated() && (!("organizationId" in request.resource.data) || isAnOrgEditor(getOrganization(request.resource.data.organizationId)));
            allow update: if authenticated() && (isAdmin(resource.data) || isAnOrgEditor(getOrganization(resource.data.organizationId))) && (isEmailVerified() || isPhoneNumberAccount());
            allow delete: if authenticated() && (isAdmin(resource.data) || isAnOrgAdmin(getOrganization(resource.data.organizationId))) && (isEmailVerified() || isPhoneNumberAccount());

            // Deprecated
            match /voteItems/{voteItemsId} {
                allow list: if true;
                allow write: if authenticated() && isAdmin(projectData(projectId));
            }

            // Should be replaced by aggregated votes...
            match /sessionVotes/{session} {
                allow list: if true;
                allow read: if true;
                allow create: if authenticated() && (isAdmin(projectData(projectId)) || isAnOrgAdmin(getOrganization(projectData(projectId).organizationId))) && (isEmailVerified() || isPhoneNumberAccount()); // Should not be use by admin, but only for testing
                allow delete: if authenticated() && (isAdmin(projectData(projectId)) || isAnOrgAdmin(getOrganization(projectData(projectId).organizationId))) && (isEmailVerified() || isPhoneNumberAccount());
            }

            match /aggregatedVotes/{session} {
                allow list: if true;
                allow read: if true;
                allow create: if authenticated() && (isAdmin(projectData(projectId)) || isAnOrgAdmin(getOrganization(projectData(projectId).organizationId))) && (isEmailVerified() || isPhoneNumberAccount());  // Should not be use by admin, but only for testing
                allow delete: if authenticated() && (isAdmin(projectData(projectId)) || isAnOrgAdmin(getOrganization(projectData(projectId).organizationId))) && (isEmailVerified() || isPhoneNumberAccount());
            }

            match /userVotes/{ref} {
                allow read: if authenticated() && resource.data.userId == request.auth.uid || isAdmin(projectData(projectId)) || isAnOrgViewer(getOrganization(projectData(projectId).organizationId));
                allow create: if authenticated() && request.resource.data.userId == request.auth.uid;
                allow update: if authenticated() && resource.data.userId == request.auth.uid || isAdmin(projectData(projectId));
                allow delete: if authenticated() && (isAdmin(projectData(projectId)) || isAnOrgAdmin(getOrganization(projectData(projectId).organizationId))) && (isEmailVerified() || isPhoneNumberAccount());
            }

            match /speakers/{speakerId} {
                allow get: if true;
                allow list: if true;
                allow write: if authenticated() && (isAdmin(projectData(projectId)) ||  isAnOrgEditor(getOrganization(projectData(projectId).organizationId)));
            }

            match /talks/{talkId} {
                allow get: if true;
                allow list: if true;
                allow write: if authenticated() && (isAdmin(projectData(projectId)) ||  isAnOrgEditor(getOrganization(projectData(projectId).organizationId)));
            }
        }

        // PROJECTS INVITE
        match /invites/{inviteId} {
            allow get: if authenticated() && isDestinedToMe(resource.data);
            allow list: if authenticated() && ( ("projectId" in resource.data && isAdmin(projectData(resource.data.projectId))) || isAnOrgViewer(getOrganization(resource.data.organizationId)));
            allow create: if authenticated() && ( ("projectId" in request.resource.data && isAdmin(projectData(request.resource.data.projectId))) || isAnOrgAdmin(getOrganization(request.resource.data.organizationId)));
            allow delete: if authenticated() && ( ("projectId" in resource.data && isAdmin(projectData(resource.data.projectId))) || isAnOrgAdmin(getOrganization(resource.data.organizationId)));

            function isDestinedToMe(data) { return request.auth.token.email == data.destinationUserInfo; }
        }

        // ORGANIZATIONS

        match /organizations/{organizationId} {
            allow get: if authenticated() && (isAnOrgViewer(resource.data) || isSuperAdmin(request.auth.uid) );
            allow list: if authenticated() && (isAnOrgViewer(resource.data) || isSuperAdmin(request.auth.uid) );
            allow create: if authenticated();
            allow update: if authenticated() && ((isAnOrgAdmin(resource.data) && request.resource.data.ownerUserId == resource.data.ownerUserId) || isOrgOwner(resource.data) );
            allow delete: if authenticated() && isOrgOwner(resource.data);
        }
    }
}
