From 4b4ef4746a42a1f7c1884af4751f73107812e42b Mon Sep 17 00:00:00 2001 From: William Chong <6198816+williamchong@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:18:04 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Store=20cart=20to=20user=20db=20(#1?= =?UTF-8?q?965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Store cart to user db * 🐛 Dont clear cart on add cart items * 🐛 Overwrite cart item on recover * 🚸 Clear local cart on account restore * ♻️ Combine cart API path --- src/server/api/routes/users/v2/auth.js | 4 ++ src/server/api/routes/users/v2/cart.js | 72 +++++++++++++++++++++++++ src/server/api/routes/users/v2/index.js | 2 + src/store/modules/cart.js | 45 +++++++++++----- src/store/modules/wallet.js | 5 +- src/util/api/index.js | 4 ++ 6 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 src/server/api/routes/users/v2/cart.js diff --git a/src/server/api/routes/users/v2/auth.js b/src/server/api/routes/users/v2/auth.js index 07a381ea8..6680dad6c 100644 --- a/src/server/api/routes/users/v2/auth.js +++ b/src/server/api/routes/users/v2/auth.js @@ -32,6 +32,7 @@ router.get('/self', authenticateV2Login, async (req, res, next) => { eventLastSeenTs, lastLoginMethod: loginMethod, locale = DEFAULT_LOCALE, + cart, } = userDoc.data(); if (email) { try { @@ -53,6 +54,7 @@ router.get('/self', authenticateV2Login, async (req, res, next) => { eventLastSeenTs: eventLastSeenTs ? eventLastSeenTs.toMillis() : 1000, locale, crispToken: email ? getCrispUserHash(email) : undefined, + cart, }); } catch (err) { if (req.session) req.session = null; @@ -71,6 +73,7 @@ router.post('/login', async (req, res, next) => { loginMethod = '', gaClientId, userIdHash, + cart, } = req.body; try { if (!inputWallet || !signature || !publicKey || !message) { @@ -158,6 +161,7 @@ router.post('/login', async (req, res, next) => { displayName, isNew, crispToken: email ? getCrispUserHash(email) : undefined, + cart, }; res.json(payload); return; diff --git a/src/server/api/routes/users/v2/cart.js b/src/server/api/routes/users/v2/cart.js new file mode 100644 index 000000000..6717cec4e --- /dev/null +++ b/src/server/api/routes/users/v2/cart.js @@ -0,0 +1,72 @@ +const { Router } = require('express'); + +const { authenticateV2Login } = require('../../../middleware/auth'); +const { + FieldValue, + walletUserCollection, +} = require('../../../../modules/firebase'); + +const router = Router(); + +router.get('/cart', authenticateV2Login, async (req, res) => { + const { user } = req.session; + const cartDoc = await walletUserCollection.doc(user).get(); + const { cart } = cartDoc.data(); + res.json({ cart: cart || [] }); +}); + +router.post('/cart', authenticateV2Login, async (req, res) => { + const { user } = req.session; + const { cart: inputCart } = req.body; + if (!Array.isArray(inputCart)) { + res.status(400).send('INVALID_CART'); + return; + } + const cart = Object.values( + inputCart.reduce((acc, item) => { + acc[item.productId] = item; + return acc; + }, {}) + ).map(item => { + const { + productId, + collectionId, + classId, + coupon, + customPriceInDecimal, + from, + timestamp, + quantity, + priceIndex, + } = item; + return JSON.parse( + JSON.stringify({ + productId, + collectionId, + classId, + coupon, + customPriceInDecimal, + from, + timestamp, + quantity, + priceIndex, + }) + ); + }); + await walletUserCollection.doc(user).update({ + cart: cart?.length ? cart : FieldValue.delete(), + cartUpdateTimestamp: FieldValue.serverTimestamp(), + }); + res.sendStatus(200); +}); + +router.delete('/cart', authenticateV2Login, async (req, res) => { + const { user } = req.session; + await walletUserCollection.doc(user).update({ + cart: FieldValue.delete(), + cartUpdateTimestamp: FieldValue.serverTimestamp(), + }); + res.sendStatus(200); +}); + +module.exports = router; diff --git a/src/server/api/routes/users/v2/index.js b/src/server/api/routes/users/v2/index.js index 8615cba03..73643dcca 100644 --- a/src/server/api/routes/users/v2/index.js +++ b/src/server/api/routes/users/v2/index.js @@ -1,6 +1,7 @@ const { Router } = require('express'); const auth = require('./auth'); +const cart = require('./cart'); const email = require('./email'); const event = require('./event'); const follow = require('./follow'); @@ -12,6 +13,7 @@ const wallet = require('./wallet'); const router = Router(); router.use(auth); +router.use(cart); router.use(email); router.use(event); router.use(follow); diff --git a/src/store/modules/cart.js b/src/store/modules/cart.js index dc75891d5..2af58bfc6 100644 --- a/src/store/modules/cart.js +++ b/src/store/modules/cart.js @@ -5,6 +5,7 @@ import { saveShoppingCartToStorage, } from '~/util/shopping-cart'; import { BATCH_COLLECT_MAX } from '~/constant'; +import { postShoppingCart, deleteShoppingCart } from '~/util/api'; import * as TYPES from '../mutation-types'; const state = () => ({ @@ -34,7 +35,10 @@ const mutations = { [TYPES.SHOPPING_CART_REPLACE_ALL_NFT_CLASS](state, map) { state.shoppingCartNFTClassByIdMap = map; }, - [TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT](state, newItem) { + [TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT]( + state, + { item: newItem, overwrite = false } + ) { const { collectionId, classId, @@ -57,7 +61,7 @@ const mutations = { quantity: 1, priceIndex: priceIndex ?? (classId ? 0 : undefined), }; - } else { + } else if (!overwrite) { item = { ...item, quantity: item.quantity + 1, @@ -151,13 +155,12 @@ const actions = { if (getters.shoppingCartBookProductList.length >= BATCH_COLLECT_MAX) { return; } - commit(TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT, item); + commit(TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT, { item }); dispatch('saveBookProductShoppingCart'); }, addBookProductsToShoppingCart({ commit, dispatch }, items) { - commit(TYPES.SHOPPING_CART_REPLACE_ALL_BOOK_PRODUCT, {}); items.slice(0, BATCH_COLLECT_MAX).forEach(item => { - commit(TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT, item); + commit(TYPES.SHOPPING_CART_ADD_BOOK_PRODUCT, { item, overwrite: true }); }); dispatch('saveBookProductShoppingCart'); }, @@ -169,14 +172,32 @@ const actions = { commit(TYPES.SHOPPING_CART_REPLACE_ALL_BOOK_PRODUCT, {}); dispatch('saveBookProductShoppingCart'); }, - saveBookProductShoppingCart({ state }) { - saveShoppingCartToStorage(state.shoppingCartBookProductByIdMap, 'book'); + async saveBookProductShoppingCart({ state, getters }) { + if (getters.loginAddress) { + const cart = Object.values(state.shoppingCartBookProductByIdMap); + try { + if (cart.length) { + await this.$api.$post(postShoppingCart(), { cart }); + } else { + await this.$api.$delete(postShoppingCart()); + } + saveShoppingCartToStorage({}, 'book'); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + saveShoppingCartToStorage(state.shoppingCartBookProductByIdMap, 'book'); + } + } else { + saveShoppingCartToStorage(state.shoppingCartBookProductByIdMap, 'book'); + } }, - loadBookProductShoppingCart({ commit }) { - commit( - TYPES.SHOPPING_CART_REPLACE_ALL_BOOK_PRODUCT, - loadShoppingCartFromStorage('book') - ); + loadBookProductShoppingCart({ commit, getters }) { + if (!getters.shoppingCartBookItems.length) { + commit( + TYPES.SHOPPING_CART_REPLACE_ALL_BOOK_PRODUCT, + loadShoppingCartFromStorage('book') + ); + } }, }; diff --git a/src/store/modules/wallet.js b/src/store/modules/wallet.js index 7e5bbc3e8..886d69b6d 100644 --- a/src/store/modules/wallet.js +++ b/src/store/modules/wallet.js @@ -840,13 +840,16 @@ const actions = { if (!checkIsLikeCoinAppInAppBrowser(this.$router.app.$route)) { await dispatch('setLocale', userInfo.locale); } - const { displayName, email, crispToken } = userInfo; + const { displayName, email, crispToken, cart } = userInfo; updateLoggerUserInfo(this, { email, displayName, wallet: state.address, crispToken, }); + if (cart?.length) { + dispatch('addBookProductsToShoppingCart', cart); + } return userInfo; }, async walletFetchSessionUserData( diff --git a/src/util/api/index.js b/src/util/api/index.js index 3302cfeb1..942eb92a1 100644 --- a/src/util/api/index.js +++ b/src/util/api/index.js @@ -454,6 +454,10 @@ export const getUserV2Self = () => '/api/v2/users/self'; export const postUserV2Login = () => '/api/v2/users/login'; export const postUserV2Logout = () => '/api/v2/users/logout'; +export const getShoppingCart = () => '/api/v2/users/cart'; +export const postShoppingCart = getShoppingCart; +export const deleteShoppingCart = getShoppingCart; + export const postUserV2WalletEmail = ({ email, followee,