import { createStore } from "vuex"; import { gapi, client } from "@/gapi"; import { signOut, GoogleAuthProvider, signInWithCredential, } from "firebase/auth"; import { collection, addDoc, query, where, limit, getDocs, doc, deleteDoc, updateDoc, } from "firebase/firestore"; import { firebaseAuth, firebaseFirestore } from "@/firebase.js"; export default createStore({ state: { user: null, active: false, access_token: null, docs: [], loading: false, errorMessage: "", API_ENDPOINT: "https://auto-archiver-api.bellingcat.com" }, mutations: { setUser(state, user) { state.user = user; }, setUserActiveState(state, active) { state.user.active = active; }, setDocs(state, docs) { state.docs = docs; }, setLoading(state, loading) { state.loading = loading; }, setAccessToken(state, access_token) { state.access_token = access_token; }, setErrorMessage(state, errorMessage) { state.errorMessage = errorMessage; }, }, actions: { async signin({ commit, dispatch }) { async function callback(tokenResponse) { let access_token = tokenResponse.access_token; commit("setAccessToken", access_token); const credential = GoogleAuthProvider.credential(null, access_token); const response = await signInWithCredential(firebaseAuth, credential); commit("setUser", response.user); dispatch("checkActiveUser"); dispatch("getDocs"); } commit("setUser", null); const client = google.accounts.oauth2.initTokenClient({ client_id: "406209235111-r1mpkvkfaqc2jg5iqbvffl2b0rf4clbo.apps.googleusercontent.com", scope: "https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", callback, }); client.requestAccessToken(); }, async signout({ commit }) { console.log("sign out"); try { await gapi.auth2.getAuthInstance().signOut(); console.log("User is signed out from gapi."); await signOut(firebaseAuth); console.log("User is signed out from firebase."); // clean user from store commit("setUser", null); commit("setDocs", []); } catch (error) { console.error("signOutUser (firebase/auth.js): ", error); } }, async checkActiveUser({ state, commit }) { try { commit("setErrorMessage", ""); const r = await fetch( `${state.API_ENDPOINT}/user/active`, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${state.access_token}`, }, } ); const response = await r.json(); commit("setUserActiveState", response.active); } catch (error) { console.error("checkActiveUser (firebase.js): ", error); commit("setErrorMessage", "Unable to check user status against the API"); } }, async getDocs({ state, commit }) { if (!state.user || !state.user.active) { return; } try { // get documents where uid matches user const q = query( collection(firebaseFirestore, "sheets"), where("uid", "==", state.user.uid) ); const response = await getDocs(q); const docs = response.docs.map((d) => ({ id: d.id, ...d.data() })); commit("setDocs", docs); commit("setLoading", false); } catch (error) { console.error("getDocs (firebase.js): ", error); } }, async removeDoc({ dispatch }, id) { try { await deleteDoc(doc(firebaseFirestore, "sheets", id)); dispatch("getDocs"); } catch (error) { console.error("removeDocs (firebase.js): ", error); } }, async archive({ state, dispatch }, sheet) { try { // send a post request to the API with the sheet ID in the body // and a bearer auth token in the header await fetch( `${state.API_ENDPOINT}/sheet`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${state.access_token}`, }, body: JSON.stringify({ sheet_id: sheet.sheetId, }), } ); // update firestore with the archive status const docRef = doc(firebaseFirestore, "sheets", sheet.id); await updateDoc(docRef, { lastArchived: Date.now(), }); // update the store dispatch("getDocs"); } catch (error) { console.error("archive (firebase.js): ", error); } }, async add({ state, dispatch, commit }, { name }) { commit("setLoading", true); try { // create new sheet const newSheet = await gapi.client.sheets.spreadsheets.create({ properties: { title: name, }, }); const spreadsheetId = newSheet.result.spreadsheetId; const userEnteredFormat = { textFormat: { bold: true, }, }; // add header row await gapi.client.sheets.spreadsheets.batchUpdate( { spreadsheetId: spreadsheetId, }, { requests: [ { updateCells: { rows: [ { values: [ { userEnteredValue: { stringValue: "Link", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Archive status", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Destination folder", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Archive location", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Archive date", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Thumbnail", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Upload timestamp", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Upload title", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Textual content", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Screenshot", }, userEnteredFormat, }, { userEnteredValue: { stringValue: "Hash", }, userEnteredFormat, }, // { // userEnteredValue: { // stringValue: "WACZ", // }, // userEnteredFormat, // }, // { // userEnteredValue: { // stringValue: "Replaywebpage", // }, // userEnteredFormat, // }, ], }, ], fields: "userEnteredValue.stringValue,userEnteredFormat.textFormat.bold", start: { sheetId: 0, rowIndex: 0, columnIndex: 0, }, }, }, { addProtectedRange: { protectedRange: { range: { sheetId: 0, startRowIndex: 0, endRowIndex: 1, startColumnIndex: 0, endColumnIndex: 11, }, description: "Protecting header row (needed for auto-archiver), do not modify archiving column names, you can add and move columns around when no 'Archive in Progress' is present in the 'Archive status' column.", warningOnly: true, }, }, }, ], } ); // add permissions await gapi.client.drive.permissions.create({ fileId: spreadsheetId, resource: { role: "writer", type: "user", emailAddress: "bellingcat-auto-archiver-api@bellingcat-auto-archiver-b85db.iam.gserviceaccount.com", }, }); const col = await collection(firebaseFirestore, "sheets"); await addDoc(col, { sheetId: spreadsheetId, url: newSheet.result.spreadsheetUrl, timestamp: Date.now(), uid: state.user.uid, email: state.user.email, lastArchived: null, name: name, }); dispatch("getDocs"); } catch (error) { console.error("add (firebase.js): ", error); } }, async enable({ state, dispatch, commit }, { spreadsheetId }) { commit("setLoading", true); commit("setErrorMessage", ""); try { // fetch existing sheet console.log(spreadsheetId); const sheetToEnable = await gapi.client.sheets.spreadsheets.get({ spreadsheetId: spreadsheetId, }); console.log(sheetToEnable); const q = query( collection(firebaseFirestore, "sheets"), where("uid", "==", state.user.uid), where("sheetId", "==", spreadsheetId), limit(1) ); const response = await getDocs(q); if (response.docs.length > 0) { throw "Sheet already enabled"; } const col = await collection(firebaseFirestore, "sheets"); await addDoc(col, { sheetId: spreadsheetId, url: sheetToEnable.result.spreadsheetUrl, timestamp: Date.now(), uid: state.user.uid, email: state.user.email, lastArchived: null, name: sheetToEnable.result.properties.title, }); dispatch("getDocs"); } catch (error) { commit("setErrorMessage", `Unable to add sheet: ${JSON.stringify(error)}`); commit("setLoading", false); console.error("add (firebase.js): ", error); } }, }, });