Monorepo containing core modules of Zeichen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
3.6 KiB

  1. import { addTime, TimeDivision } from '../utilities/Date'
  2. import * as Serialization from '../utilities/Serialization'
  3. import NoteModel from '../models/Note'
  4. import * as ColumnTypes from '../utilities/ColumnTypes'
  5. import LocalStorage from './LocalStorage'
  6. type StorageParams = {
  7. id: string,
  8. url: string,
  9. }
  10. type Note = ColumnTypes.InferModel<typeof NoteModel>
  11. type LoadItems = <T extends Record<string, unknown>>(params: StorageParams) => () => Promise<T[]>
  12. const loadItems: LoadItems = <T extends Record<string, unknown>>(params) => async (): Promise<T[]> => {
  13. const { id, url, } = params
  14. const storage = new LocalStorage(
  15. window.localStorage,
  16. Serialization.serialize,
  17. Serialization.deserialize
  18. )
  19. const localData = storage.getItem(id)
  20. if (localData === null) {
  21. const remoteItems = await window.fetch(url)
  22. // TODO add custom serialization method
  23. const theItems: T[] = await remoteItems.json()
  24. storage.setItem(id, {
  25. // TODO backend should set expiry
  26. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  27. items: theItems,
  28. })
  29. return theItems
  30. }
  31. const dataExpiry = new Date(Number(localData.expiry))
  32. const now = new Date()
  33. if (now.getTime() > dataExpiry.getTime()) {
  34. storage.removeItem(id)
  35. const loader: () => Promise<T[]> = loadItems(params)
  36. return loader()
  37. }
  38. return localData.items
  39. }
  40. type SaveItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<T>
  41. const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async (item) => {
  42. const { id: storageId, url } = params
  43. const storage = new LocalStorage(
  44. window.localStorage,
  45. Serialization.serialize,
  46. Serialization.deserialize
  47. )
  48. const localData = storage.getItem(storageId, {
  49. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  50. items: [],
  51. })
  52. const { items: localItems } = localData
  53. const { id: itemId, ...theBody } = item
  54. const theItems: T[] = (
  55. localItems.some(i => i.id === itemId)
  56. ? localItems.map(i => i.id === itemId ? item : i)
  57. : [...localItems, item]
  58. )
  59. storage.setItem(storageId, { ...localData, items: theItems })
  60. const response = await window.fetch(`${url}/${itemId}`, {
  61. method: 'put',
  62. body: JSON.stringify(theBody),
  63. headers: {
  64. 'Content-Type': 'application/json',
  65. },
  66. })
  67. const responseBody = await response.json()
  68. if (response.status !== 201 && response.status !== 200) {
  69. throw responseBody
  70. }
  71. return responseBody as T
  72. }
  73. type DeleteItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<boolean>
  74. const deleteItem: DeleteItem = <T extends Record<string, unknown>>(params) => async (item) => {
  75. const { id: storageId, url } = params
  76. const storage = new LocalStorage(
  77. window.localStorage,
  78. Serialization.serialize,
  79. Serialization.deserialize
  80. )
  81. const localData = storage.getItem(storageId, {
  82. expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
  83. items: [],
  84. })
  85. const { items: localItems } = localData
  86. const { id: itemId } = item
  87. const theItems: T[] = localItems.filter(i => i.id !== itemId)
  88. storage.setItem(storageId, { ...localData, items: theItems })
  89. const response = await window.fetch(`${url}/${itemId}`, {
  90. method: 'delete',
  91. })
  92. return response.status === 204
  93. }
  94. export const loadNotes = loadItems<Note>({ id: 'notes', url: '/api/notes' })
  95. export const loadFolders = loadItems({ id: 'folders', url: '/api/folders' })
  96. export const saveNote = saveItem<Note>({ id: 'notes', url: '/api/notes' })
  97. export const saveFolder = saveItem({ id: 'folders', url: '/api/folders' })
  98. export const deleteNote = deleteItem<Note>({ id: 'notes', url: '/api/notes' })