Skip to content

Commit

Permalink
Tabs upgrades (#359)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiporox authored Aug 25, 2024
1 parent 163ba79 commit f342fd3
Show file tree
Hide file tree
Showing 10 changed files with 696 additions and 10 deletions.
80 changes: 78 additions & 2 deletions src/common/data/stores/app/homebase/homebaseTabsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import {
} from "@/common/components/templates/Space";
import { StoreGet, StoreSet } from "../../createStore";
import { AppStore } from "..";
import { cloneDeep, debounce, forEach, has, isArray, mergeWith } from "lodash";
import {
clone,
cloneDeep,
debounce,
forEach,
has,
isArray,
mergeWith,
} from "lodash";
import stringify from "fast-json-stable-stringify";
import axiosBackend from "@/common/data/api/backend";
import {
ManageHomebaseTabsResponse,
UnsignedManageHomebaseTabsRequest,
} from "@/pages/api/space/homebase/tabs";
import { createClient } from "@/common/data/database/supabase/clients/component";
import { homebasePath } from "@/constants/supabase";
import { homebasePath, homebaseTabOrderPath } from "@/constants/supabase";
import axios from "axios";
import { SignedFile, signSignable } from "@/common/lib/signedFiles";
import INITIAL_HOMEBASE_CONFIG from "@/constants/intialHomebase";
Expand All @@ -24,10 +32,17 @@ interface HomeBaseTabStoreState {
remoteConfig?: SpaceConfig;
};
};
tabOrdering: {
local: string[];
remote: string[];
};
}

interface HomeBaseTabStoreActions {
loadTabNames: () => Promise<string[]>;
loadTabOrdering: () => Promise<string[]>;
updateTabOrdering: (newOrdering: string[]) => void;
commitTabOrderingToDatabase: () => Promise<void> | undefined;
renameTab: (tabName: string, newName: string) => Promise<void>;
deleteTab: (tabName: string) => Promise<void>;
createTab: (tabName: string) => Promise<void>;
Expand All @@ -45,13 +60,74 @@ export type HomeBaseTabStore = HomeBaseTabStoreState & HomeBaseTabStoreActions;

export const homeBaseStoreDefaults: HomeBaseTabStoreState = {
tabs: {},
tabOrdering: {
local: [],
remote: [],
},
};

export const createHomeBaseTabStoreFunc = (
set: StoreSet<AppStore>,
get: StoreGet<AppStore>,
): HomeBaseTabStore => ({
...homeBaseStoreDefaults,
updateTabOrdering(newOrdering) {
set((draft) => {
draft.homebase.tabOrdering.local = newOrdering;
}, "updateTabOrdering");
},
async loadTabOrdering() {
const supabase = createClient();
const {
data: { publicUrl },
} = supabase.storage
.from("private")
.getPublicUrl(
`${homebaseTabOrderPath(get().account.currentSpaceIdentityPublicKey!)}}`,
);
try {
const { data } = await axios.get<Blob>(publicUrl, {
responseType: "blob",
headers: {
"Cache-Control": "no-cache",
Pragma: "no-cache",
Expires: "0",
},
});
const fileData = JSON.parse(await data.text()) as SignedFile;
const tabOrder = JSON.parse(
await get().account.decryptEncryptedSignedFile(fileData),
) as string[];
set((draft) => {
draft.homebase.tabOrdering = {
local: clone(tabOrder),
remote: clone(tabOrder),
};
}, `loadHomebaseTabOrdering`);
return tabOrder;
} catch (e) {
return [];
}
},
commitTabOrderingToDatabase: debounce(async () => {
const localCopy = cloneDeep(get().homebase.tabOrdering.local);
if (localCopy) {
const file = await get().account.createEncryptedSignedFile(
stringify(localCopy),
"json",
{ useRootKey: true },
);
try {
await axiosBackend.post(`/api/space/homebase/tabOrder/`, file);
set((draft) => {
draft.homebase.tabOrdering.remote = localCopy;
}, "commitHomebaseTabOrderToDatabase");
} catch (e) {
console.error(e);
throw e;
}
}
}, 1000),
async loadTabNames() {
try {
const { data } = await axiosBackend.get<ManageHomebaseTabsResponse>(
Expand Down
70 changes: 69 additions & 1 deletion src/common/data/stores/app/space/spaceStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ import {
AnalyticsEvent,
} from "@/common/providers/AnalyticsProvider";
import createIntialPersonSpaceConfigForFid from "@/constants/initialPersonSpace";
import {
UnsignedUpdateSpaceOrderRequest,
UpdateSpaceOrderResponse,
} from "@/pages/api/space/spaceOrder";

type SpaceId = string;

Expand Down Expand Up @@ -82,9 +86,24 @@ interface SpaceState {
remoteSpaces: Record<string, CachedSpace>;
editableSpaces: Record<SpaceId, string>;
localSpaces: Record<string, UpdatableSpaceConfig>;
spaceLookups: Record<
number,
{
local: SpaceLookupInfo[];
remote: SpaceLookupInfo[];
}
>;
}

interface SpaceLookupInfo {
spaceId: string;
name: string;
}

interface SpaceActions {
loadSpaceOrderForFid: (fid: number) => Promise<SpaceLookupInfo[]>;
updateLocalSpaceOrdering: (fid: number, newOrder: SpaceLookupInfo[]) => void;
commitSpaceOrderToDatabase: (fid: number) => Promise<void> | undefined;
loadSpace: (spaceId: string, fid: number) => Promise<void>;
registerSpace: (fid: number, name: string) => Promise<string | undefined>;
renameSpace: (spaceId: string, name: string) => Promise<void>;
Expand All @@ -103,13 +122,62 @@ export const spaceStoreDefaults: SpaceState = {
remoteSpaces: {},
editableSpaces: {},
localSpaces: {},
spaceLookups: {},
};

export const createSpaceStoreFunc = (
set: StoreSet<AppStore>,
get: StoreGet<AppStore>,
): SpaceStore => ({
...spaceStoreDefaults,
async loadSpaceOrderForFid(fid) {
try {
const { data } = await axiosBackend.get<UpdateSpaceOrderResponse>(
`/api/space/spaceOrdering`,
{ params: { fid } },
);
if (data && data.result === "success") {
set((draft) => {
draft.space.spaceLookups[fid].local = cloneDeep(data.value!);
draft.space.spaceLookups[fid].remote = cloneDeep(data.value!);
}, "commitSpaceOrderToDatabase");
}
return data.value!;
} catch (e) {
console.error(e);
return [];
}
},
updateLocalSpaceOrdering(fid, newOrder) {
set((draft) => {
draft.space.spaceLookups[fid].local = newOrder;
}, "updateLocalSpaceOrdering");
},
commitSpaceOrderToDatabase: debounce(async (fid) => {
const unsignedRequest: UnsignedUpdateSpaceOrderRequest = {
timestamp: moment().toISOString(),
identityPublicKey: get().account.currentSpaceIdentityPublicKey!,
ordering: map(get().space.spaceLookups[fid].local, (i) => i.spaceId),
fid,
};
const signedRequest = signSignable(
unsignedRequest,
get().account.getCurrentIdentity()!.rootKeys.privateKey,
);
try {
const { data } = await axiosBackend.post<UpdateSpaceOrderResponse>(
`/api/space/spaceOrdering`,
signedRequest,
);
if (data && data.result === "success") {
set((draft) => {
draft.space.spaceLookups[fid].remote = data.value!;
}, "commitSpaceOrderToDatabase");
}
} catch (e) {
console.error(e);
}
}, 1000),
loadSpace: async (spaceId, fid) => {
// TO DO: skip if cached copy is recent enough
try {
Expand Down Expand Up @@ -176,7 +244,6 @@ export const createSpaceStoreFunc = (
spaceName: name,
timestamp: moment().toISOString(),
fid,
isDefault: true,
};
const registration = signSignable(
unsignedRegistration,
Expand Down Expand Up @@ -308,4 +375,5 @@ export const partializedSpaceStore = (state: AppStore): SpaceState => ({
remoteSpaces: state.space.remoteSpaces,
editableSpaces: state.space.editableSpaces,
localSpaces: state.space.localSpaces,
spaceLookups: state.space.spaceLookups,
});
4 changes: 4 additions & 0 deletions src/constants/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ export function authenticatorsPath(identityPublicKey: string) {
export function homebasePath(identityPublicKey: string) {
return `${identityPublicKey}/homebase`;
}

export function homebaseTabOrderPath(key: string) {
return `${homebasePath(key)}TabOrder`;
}
67 changes: 67 additions & 0 deletions src/pages/api/space/homebase/tabOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import requestHandler, {
NounspaceResponse,
} from "@/common/data/api/requestHandler";
import {
SignedFile,
isSignedFile,
validateSignable,
} from "@/common/lib/signedFiles";
import { NextApiRequest, NextApiResponse } from "next/types";
import supabase from "@/common/data/database/supabase/clients/server";
import stringify from "fast-json-stable-stringify";
import { homebaseTabOrderPath } from "@/constants/supabase";

export type UpdateTabOrderResponse = NounspaceResponse<boolean>;
export type UpdateTabOrderRequest = SignedFile;

async function updateTabOrder(
req: NextApiRequest,
res: NextApiResponse<UpdateTabOrderResponse>,
) {
const file: UpdateTabOrderRequest = req.body;
if (!isSignedFile(file)) {
res.status(400).json({
result: "error",
error: {
message:
"Config must contain publicKey, fileData, fileType, isEncrypted, and timestamp",
},
});
return;
}
if (!validateSignable(file)) {
res.status(400).json({
result: "error",
error: {
message: "Invalid signature",
},
});
return;
}
const { error } = await supabase.storage
.from("private")
.upload(
homebaseTabOrderPath(file.publicKey),
new Blob([stringify(file)], { type: "application/json" }),
{
upsert: true,
},
);
if (error) {
res.status(500).json({
result: "error",
error: {
message: error.message,
},
});
return;
}
res.status(200).json({
result: "success",
value: true,
});
}

export default requestHandler({
post: updateTabOrder,
});
Loading

0 comments on commit f342fd3

Please sign in to comment.