Gql_promis_ copyexperimental_prog.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <Header>Gql</Header>
  2. <body>
  3. <script>
  4. function jwtDecode(token) { // расщифровки токена авторизации
  5. if (!token || typeof token != "string")
  6. return undefined;
  7. let tokenArr = token.split(".");
  8. if (tokenArr.length != 3)
  9. return undefined;
  10. try {
  11. let tokenJsonStr = atob(tokenArr[1]);
  12. let tokenJson = JSON.parse(tokenJsonStr);
  13. return tokenJson;
  14. }
  15. catch {
  16. return undefined;
  17. }
  18. }
  19. function combineReducers(reducers) {
  20. function totalReducer(totalState = {}, action) {
  21. const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
  22. //цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
  23. for (const [reducerName, childReducer] of Object.entries(reducers)) {
  24. const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
  25. if (newState !== totalState[reducerName]) { //если он отреагировал на action
  26. newTotalState[reducerName] = newState //добавляем его в newTotalState
  27. }
  28. }
  29. //Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
  30. if (Object.values(newTotalState).length) {
  31. return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на старые
  32. }
  33. return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
  34. }
  35. return totalReducer
  36. }
  37. function cartReducer(state = {}, action) { // диспетчер обработки
  38. switch (action.type) {
  39. case 'CART_ADD':
  40. if (action.count >= 0) {
  41. let newState = { ...state };
  42. let { count } = state[action.good._id] ?? { count: 0 };
  43. newState[action.good._id] = { count: action.count + count, good: { ...action.good } }
  44. return newState;
  45. }
  46. case 'CART_SUB':
  47. if (action.count >= 0) {
  48. let newState = { ...state };
  49. let { count } = state[action.good._id] ?? { count: 0 };
  50. if (count >= action.count) {
  51. newState[action.good._id] = { count: action.count - count, good: { ...action.good } }
  52. return newState;
  53. }
  54. }
  55. break;
  56. case 'CART_DEL':
  57. {
  58. let newState = { ...state };
  59. delete newState[action.good._id];
  60. return newState;
  61. }
  62. case 'CART_SET':
  63. {
  64. let newState = { ...state };
  65. newState[action.good._id] = { count: action.count, good: { ...action.good } };
  66. return newState;
  67. }
  68. case 'CART_CLEAR':
  69. return {};
  70. }
  71. return state;
  72. }
  73. function localStoredReducer(originalReducer, localStorageKey) {
  74. let firstRun = true;
  75. function wrapper(state, action) {
  76. if (firstRun) {
  77. firstRun = false;
  78. try {
  79. let state = JSON.parse(localStorage[localStorageKey]);
  80. if (state.authToken)
  81. return state;
  82. }
  83. catch { }
  84. }
  85. let res = originalReducer(state, action);
  86. localStorage[localStorageKey] = JSON.stringify(res);
  87. return res;
  88. }
  89. return wrapper
  90. }
  91. function promiseReducer(state = {}, action) { // диспетчер обработки
  92. if (action) {
  93. if (action.type === 'PROMISE') {
  94. let newState = { ...state };
  95. newState[action.name] = { status: action.status, payload: action.payload, error: action.error };
  96. return newState;
  97. }
  98. }
  99. return state;
  100. }
  101. function authReducer(state = {}, action) { // диспетчер обработки login
  102. if (action) {
  103. if (action.type === 'AUTH_LOGIN') {
  104. let newState = { ...state };
  105. newState.token = action.token;
  106. newState.payload = jwtDecode(action.token);
  107. if (!newState.payload) {
  108. newState.token = undefined;
  109. }
  110. window.localStorage.authToken = newState.token;
  111. return newState;
  112. }
  113. else if (action.type === 'AUTH_LOGOUT') {
  114. let newState = { ...state };
  115. newState.token = undefined;
  116. newState.payload = undefined;
  117. return newState;
  118. }
  119. else
  120. localStorage.authToken = undefined;
  121. }
  122. return state;
  123. }
  124. function createStore(reducer) {
  125. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  126. let cbs = [] //массив подписчиков
  127. const getState = () => { return state; } //функция, возвращающая переменную из замыкания
  128. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  129. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  130. function dispatch(action) {
  131. if (typeof action === 'function') { //если action - не объект, а функция
  132. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  133. }
  134. const newState = reducer(state, action) //пробуем запустить редьюсер
  135. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  136. state = newState //если смог, то обновляем state
  137. for (let cb of cbs) cb() //и запускаем подписчиков
  138. }
  139. }
  140. return {
  141. getState, //добавление функции getState в результирующий объект
  142. dispatch,
  143. subscribe //добавление subscribe в объект
  144. }
  145. }
  146. function getGql(url) {
  147. return function gql(query, vars = undefined) {
  148. let fetchSettings =
  149. {
  150. method: "POST",
  151. headers:
  152. {
  153. "Content-Type": "application/json",
  154. "Accept": "application/json"
  155. },
  156. body: JSON.stringify(
  157. {
  158. query: query,
  159. variables: vars
  160. })
  161. };
  162. if (window.localStorage.authToken) {
  163. fetchSettings.headers["Authorization"] = `Bearer ${window.localStorage.authToken}`;
  164. }
  165. return fetch(url, fetchSettings)
  166. .then(res => {
  167. if (!res.ok) {
  168. throw Error(res.statusText);
  169. }
  170. return res.json();
  171. });
  172. }
  173. }
  174. function gql(url, query, vars) {
  175. let fetchSettings =
  176. {
  177. method: "POST",
  178. headers:
  179. {
  180. "Content-Type": "application/json",
  181. "Accept": "application/json"
  182. },
  183. body: JSON.stringify(
  184. {
  185. query: query,
  186. variables: vars
  187. })
  188. };
  189. return fetch(url, fetchSettings).then(res => res.json());
  190. }
  191. const actionPromiseGql = (name, promise) => {
  192. return actionPromiseGqlInt = async (dispatch) => {
  193. dispatch(actionPending(name)) //сигнализируем redux, что промис начался
  194. try {
  195. const payload = await promise //ожидаем промиса
  196. dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
  197. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  198. }
  199. catch (error) {
  200. dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  201. }
  202. }
  203. }
  204. /*function actionAuthGql(promise) {
  205. return async function Exec(dispatch) {
  206. try {
  207. const payload = await promise //ожидаем промиса;
  208. let result = Object.values(payload.data)[0];
  209. dispatch(actionAuthLogin(result)); //сигнализируем redux, что промис успешно выполнен
  210. return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  211. }
  212. catch (error) {
  213. dispatch(actionLogOut()) //в случае ошибки - сигнализируем redux, что промис несложился
  214. }
  215. };
  216. }*/
  217. const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
  218. const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
  219. const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });
  220. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
  221. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  222. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count: count, good: good });
  223. const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good }); //Уменьшение количества товара. Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным
  224. const actionCartDel = (good) => ({ type: 'CART_DEL', good }); //Удаление товара. Должен удалять ключ из state
  225. const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good }); //Задание количества товара. В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху (или создает новый ключ, если в корзине товара не было). Если count 0 или отрицательное число - удаляем ключ из корзины;
  226. const actionCartClear = () => ({ type: 'CART_CLEAR' }); //Очистка корзины. state должен стать пустым объектом {}
  227. ///////////////////////////////////////
  228. const gqlRootCats = () => {
  229. const catQuery = `query roots {
  230. CategoryFind(query: "[{\\"parent\\": null }]") {
  231. _id name
  232. }}`;
  233. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery);
  234. }
  235. const actionRootCats = () =>
  236. actionPromiseGql('rootCats', gqlRootCats());
  237. const gqlCategoryFindOne = (id) => {
  238. const catQuery = `query CategoryFindOne($q: String) {
  239. CategoryFindOne(query: $q) {
  240. _id name
  241. parent { _id name }
  242. subCategories { _id name }
  243. goods { _id name price description
  244. images { url }
  245. }
  246. }
  247. }`;
  248. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  249. }
  250. const actionCategoryFindOne = (id) =>
  251. actionPromiseGql('catFindOne', gqlCategoryFindOne(id));
  252. const gqlGoodFindOne = (id) => {
  253. const catQuery = `
  254. query GoodFindOne($q: String) {
  255. GoodFindOne(query: $q) {
  256. _id name price description
  257. images {
  258. url
  259. }
  260. }
  261. }
  262. `;
  263. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  264. }
  265. const actionGoodFindOne = (id) =>
  266. actionPromiseGql('goodsFindOne', gqlGoodFindOne(id));
  267. //////////////////////////////////
  268. const actionLogin = (login, password) => {
  269. const upsertQuery = `query login($login:String, $password:String){
  270. login(login:$login, password:$password)
  271. }`;
  272. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", upsertQuery, { login: login, password: password });
  273. }
  274. const actionFullLogin = (login, password) => {
  275. return gqlFullLogin = async (dispatch) => {
  276. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  277. //так как actionPromise возвращает асинхронную функцию
  278. let promiseResult = dispatch((dispatch) => actionLogin(login, password));
  279. let res = await promiseResult;
  280. if (res && res.data) {
  281. let token = Object.values(res.data)[0];
  282. if (token && typeof token == 'string')
  283. return dispatch(actionAuthLogin(token));
  284. }
  285. //проверьте что token - строка и отдайте его в actionAuthLogin
  286. }
  287. }
  288. ////////////////////////////////////////
  289. const actionAuthUpsert = (login, password) => {
  290. const loginQuery = `mutation UserRegistration($login: String, $password: String) {
  291. UserUpsert(user: {login: $login, password: $password}) {
  292. _id createdAt
  293. }
  294. }`;
  295. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", loginQuery, { login: login, password: password });
  296. }
  297. const actionFullAuthUpsert = (login, password) => {
  298. return gqlFullAuthUpsert = async (dispatch) => {
  299. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  300. //так как actionPromise возвращает асинхронную функцию
  301. let promiseResult = dispatch((dispatch) => actionAuthUpsert(login, password));
  302. let res = await promiseResult;
  303. dispatch(actionFullLogin(login, password));
  304. //проверьте что token - строка и отдайте его в actionAuthLogin
  305. }
  306. }
  307. ////////////////////////////////////////
  308. const orderUpsert = (order, id = null) => {
  309. const orderUpsertQuery = `mutation OrderUpsert($order: OrderInput) {
  310. OrderUpsert(order: $order) {
  311. _id
  312. }
  313. }`;
  314. let gqlFunc = getGql("http://shop-roles.node.ed.asmer.org.ua/graphql");
  315. return gqlFunc(orderUpsertQuery, { order: { "_id": id, "orderGoods": order } });
  316. }
  317. const orderFullUpsert = () => {
  318. return gqlFullOrderUpsert = async (dispatch, getState) => {
  319. let state = getState();
  320. let order = [];
  321. for (cartItem of Object.values(state.cartReducer)) {
  322. //{count: 3, good: {_id: "xxxx" }}
  323. order.push({ good: { _id: cartItem.good._id }, count: cartItem.count });
  324. }
  325. if (order.length == 0)
  326. return;
  327. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  328. //так как actionPromise возвращает асинхронную функцию
  329. let promiseResult = orderUpsert(order);
  330. let res = await promiseResult;
  331. if (res && res.errors && res.errors.length > 0) {
  332. throw res.errors[0];
  333. }
  334. dispatch(actionCartClear());
  335. //проверьте что token - строка и отдайте его в actionAuthLogin
  336. }
  337. }
  338. const gqlFindOrders = () => {
  339. const findOrdersQuery = `query OrderFind {
  340. OrderFind(query: "[{}]") {
  341. _id total
  342. orderGoods {
  343. _id price count total
  344. good {
  345. name
  346. }
  347. }
  348. }
  349. }`;
  350. return getGql("http://shop-roles.node.ed.asmer.org.ua/graphql")(findOrdersQuery);
  351. }
  352. const actionFindOrders = () =>
  353. actionPromiseGql('orders', gqlFindOrders());
  354. ///////////////////////////////////////////////////
  355. const store = createStore(combineReducers({ promiseReducer, authReducer, cartReducer: localStoredReducer(cartReducer, 'cart') }));
  356. const delay = (ms, action) => new Promise(ok => setTimeout(() => {
  357. action();
  358. ok(ms);
  359. }, ms));
  360. store.subscribe(() => {
  361. console.log(store.getState());
  362. });
  363. //////////////////////////////////////////////
  364. //store.dispatch(actionRootCats());
  365. //store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
  366. //store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
  367. //store.dispatch(actionFullLogin("Berg", "123456789"));
  368. //store.dispatch(actionFullAuthUpsert("Berg1", "12345678911"));
  369. //store.dispatch(actionCartAdd({ _id: '62d30938b74e1f5f2ec1a124', price: 50 }));
  370. //delay(3000, ()=>store.dispatch(orderFullUpsert()));
  371. delay(3000, () => store.dispatch(actionFindOrders()));
  372. </script>
  373. </body>