|
|
|
|
@ -46,25 +46,27 @@ interface TCDB extends DBSchema {
|
|
|
|
|
|
|
|
|
|
export const db = await openDB<TCDB>("tc", 1, {
|
|
|
|
|
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
|
|
|
|
|
if(!db.objectStoreNames.contains("stores")) {
|
|
|
|
|
if (!db.objectStoreNames.contains("stores")) {
|
|
|
|
|
const store = db.createObjectStore("stores", { keyPath: "id" });
|
|
|
|
|
store.createIndex("by-type", "type");
|
|
|
|
|
store.createIndex("by-owner", "owner_id");
|
|
|
|
|
store.createIndex("by-type-and-owner", ["type", "owner_id"]);
|
|
|
|
|
store.createIndex("by-name-and-type", ["name", "type"]);
|
|
|
|
|
}
|
|
|
|
|
if(!db.objectStoreNames.contains("changes")) {
|
|
|
|
|
if (!db.objectStoreNames.contains("changes")) {
|
|
|
|
|
db.createObjectStore("changes", { keyPath: "id" });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const eventTarget = new EventTarget();
|
|
|
|
|
|
|
|
|
|
export async function trySync() {
|
|
|
|
|
const pingResult = await ping();
|
|
|
|
|
if(!pingResult) {
|
|
|
|
|
console.warn("[STORES] [trySync] LNV server is not reachable, skipping sync");
|
|
|
|
|
if (!pingResult) {
|
|
|
|
|
console.warn(
|
|
|
|
|
"[STORES] [trySync] LNV server is not reachable, skipping sync",
|
|
|
|
|
);
|
|
|
|
|
return { success: false, message: "LNV server is not reachable" };
|
|
|
|
|
}
|
|
|
|
|
await syncStores();
|
|
|
|
|
@ -72,38 +74,58 @@ export async function trySync() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function syncStores() {
|
|
|
|
|
if(!(await hasCapability("stores"))) {
|
|
|
|
|
if (!(await hasCapability("stores"))) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const changes = await Promise.all(await db.getAll("changes").then(changes => changes.map(async change => {
|
|
|
|
|
const storeData = await db.get("stores", change.id);
|
|
|
|
|
return { id: change.id, operation: change.operation, data: storeData?.data || null, modified_at: storeData?.modified_at || new Date().toISOString(), type: storeData?.type || "location", name: storeData?.name || "" };
|
|
|
|
|
})));
|
|
|
|
|
const stores = await db.getAll("stores").then(stores => stores.map(store => ({ id: store.id, modified_at: store.modified_at })));
|
|
|
|
|
const changes = await Promise.all(
|
|
|
|
|
await db.getAll("changes").then((changes) =>
|
|
|
|
|
changes.map(async (change) => {
|
|
|
|
|
const storeData = await db.get("stores", change.id);
|
|
|
|
|
return {
|
|
|
|
|
id: change.id,
|
|
|
|
|
operation: change.operation,
|
|
|
|
|
data: storeData?.data || null,
|
|
|
|
|
modified_at: storeData?.modified_at || new Date().toISOString(),
|
|
|
|
|
type: storeData?.type || "location",
|
|
|
|
|
name: storeData?.name || "",
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
const stores = await db
|
|
|
|
|
.getAll("stores")
|
|
|
|
|
.then((stores) =>
|
|
|
|
|
stores.map((store) => ({ id: store.id, modified_at: store.modified_at })),
|
|
|
|
|
);
|
|
|
|
|
const res = await authFetch(LNV_SERVER + "/stores/sync", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
changes,
|
|
|
|
|
stores
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
if(!res.ok) {
|
|
|
|
|
console.error("[STORES] [syncStores] Failed to sync stores:", await res.text());
|
|
|
|
|
stores,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
console.error(
|
|
|
|
|
"[STORES] [syncStores] Failed to sync stores:",
|
|
|
|
|
await res.text(),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data = await res.json() as { changes: Store[]; };
|
|
|
|
|
const data = (await res.json()) as { changes: Store[] };
|
|
|
|
|
const tx = db.transaction(["stores", "changes"], "readwrite");
|
|
|
|
|
// Apply all changes the server sent us
|
|
|
|
|
for(const store of data.changes) {
|
|
|
|
|
if(store.data === null) {
|
|
|
|
|
for (const store of data.changes) {
|
|
|
|
|
if (store.data === null) {
|
|
|
|
|
await tx.objectStore("stores").delete(store.id);
|
|
|
|
|
} else {
|
|
|
|
|
await tx.objectStore("stores").put(store);
|
|
|
|
|
}
|
|
|
|
|
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
|
|
|
|
|
eventTarget.dispatchEvent(
|
|
|
|
|
new CustomEvent("store-updated", { detail: store }),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// Delete all changes
|
|
|
|
|
await tx.objectStore("changes").clear();
|
|
|
|
|
@ -118,21 +140,26 @@ async function createStore(info: StoreInfo, data: object) {
|
|
|
|
|
type: info.type,
|
|
|
|
|
owner_id: "", // TODO
|
|
|
|
|
data: JSON.stringify(data),
|
|
|
|
|
modified_at: new Date().toISOString()
|
|
|
|
|
modified_at: new Date().toISOString(),
|
|
|
|
|
};
|
|
|
|
|
const tx = db.transaction(["stores", "changes"], "readwrite");
|
|
|
|
|
await tx.objectStore("stores").add(store);
|
|
|
|
|
await tx.objectStore("changes").add({ id, operation: "create" });
|
|
|
|
|
await tx.done;
|
|
|
|
|
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
|
|
|
|
|
eventTarget.dispatchEvent(
|
|
|
|
|
new CustomEvent("store-updated", { detail: store }),
|
|
|
|
|
);
|
|
|
|
|
await trySync();
|
|
|
|
|
return store;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function updateStore(info: StoreInfo, data: object | null) {
|
|
|
|
|
const store = await db.getFromIndex("stores", "by-name-and-type", [info.name, info.type]);
|
|
|
|
|
const store = await db.getFromIndex("stores", "by-name-and-type", [
|
|
|
|
|
info.name,
|
|
|
|
|
info.type,
|
|
|
|
|
]);
|
|
|
|
|
if (!store) {
|
|
|
|
|
if(data === null) return;
|
|
|
|
|
if (data === null) return;
|
|
|
|
|
return await createStore(info, data);
|
|
|
|
|
}
|
|
|
|
|
// Update the store data
|
|
|
|
|
@ -142,7 +169,9 @@ export async function updateStore(info: StoreInfo, data: object | null) {
|
|
|
|
|
await tx.objectStore("stores").put(store);
|
|
|
|
|
await tx.objectStore("changes").add({ id: store.id, operation: "update" });
|
|
|
|
|
await tx.done;
|
|
|
|
|
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
|
|
|
|
|
eventTarget.dispatchEvent(
|
|
|
|
|
new CustomEvent("store-updated", { detail: store }),
|
|
|
|
|
);
|
|
|
|
|
await trySync();
|
|
|
|
|
return store;
|
|
|
|
|
}
|
|
|
|
|
@ -166,19 +195,33 @@ export async function updateStore(info: StoreInfo, data: object | null) {
|
|
|
|
|
// return state;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
export function stores<T extends object>(type: StoreType): WrappedValue<StoreValue<T>[]> {
|
|
|
|
|
export function stores<T extends object>(
|
|
|
|
|
type: StoreType,
|
|
|
|
|
): WrappedValue<StoreValue<T>[]> {
|
|
|
|
|
const state = $state<StoreValue<T>[]>([]);
|
|
|
|
|
eventTarget.addEventListener("store-updated", async (event) => {
|
|
|
|
|
const customEvent = event as CustomEvent;
|
|
|
|
|
const updatedStore = customEvent.detail as Store;
|
|
|
|
|
if(updatedStore.type === type) {
|
|
|
|
|
if (updatedStore.type === type) {
|
|
|
|
|
const stores = await db.getAllFromIndex("stores", "by-type", type);
|
|
|
|
|
state.splice(0, state.length, ...(stores.map(store => ({ ...store, data: JSON.parse(store.data) as T })).filter(store => store.data !== null)));
|
|
|
|
|
state.splice(
|
|
|
|
|
0,
|
|
|
|
|
state.length,
|
|
|
|
|
...stores
|
|
|
|
|
.map((store) => ({ ...store, data: JSON.parse(store.data) as T }))
|
|
|
|
|
.filter((store) => store.data !== null),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
(async () => {
|
|
|
|
|
const stores = await db.getAllFromIndex("stores", "by-type", type);
|
|
|
|
|
state.splice(0, state.length, ...(stores.map(store => ({ ...store, data: JSON.parse(store.data) as T })).filter(store => store.data !== null)));
|
|
|
|
|
state.splice(
|
|
|
|
|
0,
|
|
|
|
|
state.length,
|
|
|
|
|
...stores
|
|
|
|
|
.map((store) => ({ ...store, data: JSON.parse(store.data) as T }))
|
|
|
|
|
.filter((store) => store.data !== null),
|
|
|
|
|
);
|
|
|
|
|
})();
|
|
|
|
|
return {
|
|
|
|
|
get current() {
|
|
|
|
|
@ -186,6 +229,6 @@ export function stores<T extends object>(type: StoreType): WrappedValue<StoreVal
|
|
|
|
|
},
|
|
|
|
|
set current(newValue: StoreValue<T>[]) {
|
|
|
|
|
state.splice(0, state.length, ...newValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|