mirror of
https://github.com/zoldar/jenot.git
synced 2026-01-05 07:02:55 +00:00
Make sync offline friendly
This commit is contained in:
parent
17c158b6c1
commit
d11d0b0cf1
5 changed files with 41 additions and 41 deletions
|
|
@ -13,6 +13,7 @@ defmodule Jenot.Note do
|
||||||
|
|
||||||
belongs_to(:account, Jenot.Account, type: :binary_id)
|
belongs_to(:account, Jenot.Account, type: :binary_id)
|
||||||
|
|
||||||
|
field(:server_updated_at, :utc_datetime_usec)
|
||||||
timestamps(type: :utc_datetime_usec)
|
timestamps(type: :utc_datetime_usec)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ defmodule Jenot.Note do
|
||||||
:updated_at
|
:updated_at
|
||||||
])
|
])
|
||||||
|> put_change(:id, Ecto.UUID.generate())
|
|> put_change(:id, Ecto.UUID.generate())
|
||||||
|
|> put_change(:server_updated_at, DateTime.utc_now())
|
||||||
|> validate_required([:internal_id, :type, :inserted_at, :updated_at])
|
|> validate_required([:internal_id, :type, :inserted_at, :updated_at])
|
||||||
|> put_assoc(:account, account)
|
|> put_assoc(:account, account)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ defmodule Jenot.Notes do
|
||||||
def latest_change(account) do
|
def latest_change(account) do
|
||||||
Note
|
Note
|
||||||
|> where(account_id: ^account.id)
|
|> where(account_id: ^account.id)
|
||||||
|> where([n], is_nil(n.deleted_at))
|
|> select([n], max(n.server_updated_at))
|
||||||
|> select([n], max(n.updated_at))
|
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -148,7 +147,7 @@ defmodule Jenot.Notes do
|
||||||
|> String.to_integer()
|
|> String.to_integer()
|
||||||
|> DateTime.from_unix!(:millisecond)
|
|> DateTime.from_unix!(:millisecond)
|
||||||
|
|
||||||
where(query, [n], n.updated_at > ^datetime)
|
where(query, [n], n.server_updated_at > ^datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp upsert(changeset, opts) do
|
defp upsert(changeset, opts) do
|
||||||
|
|
@ -195,6 +194,7 @@ defmodule Jenot.Notes do
|
||||||
^deleted_at,
|
^deleted_at,
|
||||||
n.deleted_at
|
n.deleted_at
|
||||||
),
|
),
|
||||||
|
server_updated_at: ^DateTime.utc_now(),
|
||||||
updated_at:
|
updated_at:
|
||||||
fragment(
|
fragment(
|
||||||
"CASE WHEN ? > ? THEN ? ELSE ? END",
|
"CASE WHEN ? > ? THEN ? ELSE ? END",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ defmodule Jenot.Repo.Migrations.InitialSchema do
|
||||||
|
|
||||||
add :account_id, references(:accounts, on_delete: :delete_all), null: false
|
add :account_id, references(:accounts, on_delete: :delete_all), null: false
|
||||||
|
|
||||||
|
add :server_updated_at, :datetime_usec, null: false
|
||||||
timestamps(type: :datetime_usec)
|
timestamps(type: :datetime_usec)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ const sync = async () => {
|
||||||
Notes.saveStorage();
|
Notes.saveStorage();
|
||||||
};
|
};
|
||||||
|
|
||||||
sync();
|
|
||||||
setInterval(sync, 5000);
|
setInterval(sync, 5000);
|
||||||
|
|
||||||
// Notifications API test - to be reused for push notifications later on
|
// Notifications API test - to be reused for push notifications later on
|
||||||
|
|
@ -57,8 +56,9 @@ const editNote = document.querySelector("#edit-note");
|
||||||
// of notes list.
|
// of notes list.
|
||||||
Notes.addEventListener("save", render.bind(this));
|
Notes.addEventListener("save", render.bind(this));
|
||||||
|
|
||||||
// Initial notes render.
|
// Initial notes render and initial sync.
|
||||||
render();
|
render();
|
||||||
|
sync();
|
||||||
|
|
||||||
// note-form component specific event handlers
|
// note-form component specific event handlers
|
||||||
newNote.addEventListener("addNote", async (e) => {
|
newNote.addEventListener("addNote", async (e) => {
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,19 @@ class WebNoteStore {
|
||||||
params.append("deleted", "true");
|
params.append("deleted", "true");
|
||||||
}
|
}
|
||||||
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
||||||
return this.#request(`${this.endpoint}api/notes${suffix}`, {}, () => []);
|
return this.#request(
|
||||||
|
`${this.endpoint}api/notes${suffix}`,
|
||||||
|
{},
|
||||||
|
() => "no_network",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id) {
|
async get(id) {
|
||||||
return this.#request(`${this.endpoint}api/notes/${id}`, {}, () => null);
|
return this.#request(
|
||||||
|
`${this.endpoint}api/notes/${id}`,
|
||||||
|
{},
|
||||||
|
() => "no_network",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(note) {
|
async add(note) {
|
||||||
|
|
@ -26,7 +34,7 @@ class WebNoteStore {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(note),
|
body: JSON.stringify(note),
|
||||||
},
|
},
|
||||||
() => null,
|
() => "no_network",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +45,7 @@ class WebNoteStore {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(note),
|
body: JSON.stringify(note),
|
||||||
},
|
},
|
||||||
() => null,
|
() => "no_network",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +182,7 @@ export class SyncedNoteStore extends EventTarget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
(async () => (skipNetwork ? null : this.webStore?.update(note)))();
|
(async () => (skipNetwork ? null : this.webStore?.add(note)))();
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -183,19 +191,33 @@ export class SyncedNoteStore extends EventTarget {
|
||||||
const that = this;
|
const that = this;
|
||||||
const meta = await this.getMeta();
|
const meta = await this.getMeta();
|
||||||
const lastSync = meta?.lastSync;
|
const lastSync = meta?.lastSync;
|
||||||
|
const currentSync = Date.now();
|
||||||
|
|
||||||
this.all(lastSync, true)
|
this.all(lastSync, true)
|
||||||
.then((notes) => {
|
.then((notes) => {
|
||||||
notes.forEach(async (n) => await that.webStore.add(n));
|
return Promise.all(notes.map((n) => that.webStore.add(n)));
|
||||||
|
})
|
||||||
|
.then((results) => {
|
||||||
|
if (results.indexOf("no_network") < 0) {
|
||||||
|
return that.webStore.all(lastSync, true);
|
||||||
|
} else {
|
||||||
|
return "no_network";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(() => that.webStore.all(lastSync, true))
|
|
||||||
.then((notes) => {
|
.then((notes) => {
|
||||||
notes.forEach(async (n) => await that.update(n, true));
|
if (notes !== "no_network") {
|
||||||
|
notes.forEach(async (n) => await that.update(n, true));
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return "no_network";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then((result) => {
|
||||||
meta.lastSync = Date.now();
|
if (result !== "no_network") {
|
||||||
|
meta.lastSync = currentSync;
|
||||||
|
|
||||||
that.setMeta(meta);
|
that.setMeta(meta);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,29 +255,4 @@ export class SyncedNoteStore extends EventTarget {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#request(url, opts, emptyValue) {
|
|
||||||
new Promise((resolve) => {
|
|
||||||
return resolve(this.#runRequest(url, opts, () => emptyValue));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async #runRequest(url, opts, errorCallback) {
|
|
||||||
opts.headers = {
|
|
||||||
...(opts.headers || {}),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, opts);
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error("Request failed", response);
|
|
||||||
return errorCallback(response);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Request error", error);
|
|
||||||
return errorCallback(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue