Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Category config and login message #83

Merged
merged 6 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions rair-front/src/components/adminViews/ServerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import { OptionsType } from '../common/commonTypes/InputSelectTypes.types';
import InputField from '../common/InputField';
import InputSelect from '../common/InputSelect';

type Category = {
name: string;
_id?: string;
files?: number;
};

const ServerSettings = ({ fullContractData }) => {
const serverSettings = useServerSettings();
const [productOptions, setProductOptions] = useState<OptionsType[]>();
const [categoryList, setCategoryList] = useState<Category[]>([]);

const [customLightModeLogo, setCustomLightModeLogo] = useState({ name: '' });
const [customDarkModeLogo, setCustomDarkModeLogo] = useState({ name: '' });
Expand Down Expand Up @@ -60,6 +67,17 @@ const ServerSettings = ({ fullContractData }) => {
serverSettings.setFooterLinks(aux);
};

const getCategories = useCallback(async () => {
const { success, result } = await rFetch('/api/categories');
if (success) {
setCategoryList(result);
}
}, []);

useEffect(() => {
getCategories();
}, [getCategories]);

const setServerSetting = useCallback(
async (setting) => {
const { success } = await rFetch(`/api/settings/`, {
Expand Down Expand Up @@ -144,6 +162,27 @@ const ServerSettings = ({ fullContractData }) => {
serverSettings.getServerSettings();
}, [serverSettings.getServerSettings]);

const deleteCategory = useCallback(
(index) => {
const aux = [...categoryList];
aux.splice(index, 1);
setCategoryList(aux);
},
[categoryList]
);

const updateCategory = useCallback(
(index) => (value) => {
const aux = [...categoryList];
aux[index] = {
...aux[index],
name: value
};
setCategoryList(aux);
},
[categoryList]
);

useEffect(() => {
serverSettings.setFeaturedProduct('null');
if (serverSettings.featuredContract === 'null') {
Expand Down Expand Up @@ -578,6 +617,73 @@ const ServerSettings = ({ fullContractData }) => {
);
})}
</div>
<div className="col-12 px-5 my-2">
<h3>Categories</h3>
{categoryList.map((categoryData, index) => {
return (
<div key={index} className="row">
<div className="col-12 col-md-10">
<InputField
customClass="rounded-rair form-control"
getter={categoryData.name}
setter={updateCategory(index)}
type="text"
/>
</div>
<button
disabled={!!categoryData.files}
onClick={() => deleteCategory(index)}
className="col-12 col-md-2 btn btn-danger">
{categoryData.files ? (
<>
{categoryData.files}{' '}
<small>files using this category</small>
</>
) : (
<i className="fa-trash fas" />
)}
</button>
</div>
);
})}
<button
className="float-start btn"
style={{
color: textColor,
background: primaryButtonColor
}}
onClick={async () => {
const result = await rFetch('/api/categories', {
method: 'POST',
body: JSON.stringify({
list: categoryList.map((item) => ({
_id: item._id,
name: item.name
}))
}),
headers: {
'Content-Type': 'application/json'
}
});
if (result.success) {
reactSwal.fire('Success', 'Categories updated', 'success');
getCategories();
}
}}>
Set
</button>
<button
className="btn btn-success float-end"
onClick={() => {
const aux = categoryList ? [...categoryList] : [];
aux.push({
name: ''
});
setCategoryList(aux);
}}>
Add
</button>
</div>
<div className="col-12 px-5 my-2">
<h3>Footer items</h3>
{serverSettings.footerLinks &&
Expand Down Expand Up @@ -657,6 +763,26 @@ const ServerSettings = ({ fullContractData }) => {
Set
</button>
</div>
<div className="col-12 px-5 my-2">
<h3>Default Signup Message</h3>
<InputField
customClass="rounded-rair form-control"
getter={serverSettings.signupMessage}
setter={serverSettings.setSignupMessage}
placeholder="Message sent through notifications"
/>
<button
className="btn rair-button"
style={{
background: secondaryButtonColor,
color: textColor
}}
onClick={() =>
setServerSetting({ signupMessage: serverSettings.signupMessage })
}>
Set
</button>
</div>
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions rair-front/src/components/adminViews/useServerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const useServerSettings = () => {
import.meta.env.VITE_NODE_ADDRESS
);
const [legal, setLegal] = useState('');
const [signupMessage, setSignupMessage] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [featuredContract, setFeaturedContract] = useState('null');
const [featuredProduct, setFeaturedProduct] = useState('null');
Expand Down Expand Up @@ -47,6 +48,7 @@ const useServerSettings = () => {
settings?.nodeAddress || import.meta.env.VITE_NODE_ADDRESS
);
setLegal(settings?.legal || '');
setSignupMessage(settings?.signupMessage || '');
if (settings.featuredCollection) {
setFeaturedContract(settings?.featuredCollection?.contract?._id);
setFeaturedProduct(settings?.featuredCollection?._id);
Expand Down Expand Up @@ -171,6 +173,8 @@ const useServerSettings = () => {
setFooterLinks,
legal,
setLegal,
signupMessage,
setSignupMessage,
blockchainSettings,
isLoading
};
Expand Down
10 changes: 9 additions & 1 deletion rair-node/bin/api/auth/auth.Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { File, User, MediaViewLog, Unlock, ServerSetting } = require('../../model
const AppError = require('../../utils/errors/AppError');
const { checkBalanceAny, checkBalanceProduct, checkAdminTokenOwns } = require('../../integrations/ethers/tokenValidation');
const { superAdminInstance } = require('../../utils/vaultSuperAdmin');
const { emitEvent } = require('../../integrations/socket.io');

module.exports = {
generateChallengeMessage: async (req, res, next) => {
Expand Down Expand Up @@ -87,7 +88,14 @@ module.exports = {
}

userData.adminRights = await checkAdminTokenOwns(userData.publicAddress);
const { superAdmins, superAdminsOnVault } = await ServerSetting.findOne({});
const { superAdmins, superAdminsOnVault, signupMessage } = await ServerSetting.findOne({});
const socket = req.app.get('socket');
emitEvent(socket)(
userData.publicAddress,
'message',
signupMessage,
[],
);
userData.superAdmin = superAdminsOnVault
? await superAdminInstance.hasSuperAdminRights(userData.publicAddress)
: superAdmins.includes(userData.publicAddress);
Expand Down
26 changes: 26 additions & 0 deletions rair-node/bin/api/categories/categories.Controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { Router } = require('express');
const {
updateCategories,
getCategories,
} = require('./categories.Service');
const { requireUserSession } = require('../../middleware/verifyUserSession');
const validation = require('../../middleware/validation');
const { verifySuperAdmin } = require('../../middleware');

const router = Router();

router.get(
'/',
getCategories,
);

router.post(
'/',
validation(['dbCategory'], 'body'),
requireUserSession,
verifySuperAdmin,
updateCategories,
getCategories,
);

module.exports = router;
63 changes: 63 additions & 0 deletions rair-node/bin/api/categories/categories.Service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { Category, File } = require('../../models');
const AppError = require('../../utils/errors/AppError');

module.exports = {
getCategories: async (req, res, next) => {
try {
const list = await Category.aggregate([{
$lookup: {
from: 'File',
localField: '_id',
foreignField: 'category',
as: 'files',
},
}, {
$project: {
_id: 1,
name: 1,
files: {
$size: '$files',
},
},
}]);
return res.json({ result: list, success: true });
} catch (error) {
return next(new AppError(error));
}
},
updateCategories: async (req, res, next) => {
try {
const currentList = await Category.find().lean();
const { list } = req.body;
// eslint-disable-next-line no-restricted-syntax
for await (const category of currentList) {
const update = list.find((item) => item?._id === category._id.toString());
if (update) {
await Category.findByIdAndUpdate(update._id, { $set: { name: update.name } });
} else {
const usingCategory = await File.findOne({
category: category._id,
});
if (!usingCategory) {
await Category.findByIdAndDelete(category._id);
}
}
}
// eslint-disable-next-line no-restricted-syntax
for await (const category of list) {
if (category._id) {
// eslint-disable-next-line no-continue
continue;
} else {
const newCat = new Category({
name: category.name,
});
await newCat.save();
}
}
return next();
} catch (error) {
return next(new AppError(error));
}
},
};
11 changes: 9 additions & 2 deletions rair-node/bin/integrations/ethers/importContractData.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,15 @@ module.exports = {
limit,
],
);
const ownerResponse = await alchemySDK.nft.getOwnersForNft(nft.contract.address, nft.tokenId);
[nft.owner] = ownerResponse.owners;
try {
const ownerResponse = await alchemySDK.nft.getOwnersForNft(
nft.contract.address,
nft.tokenId,
);
[nft.owner] = ownerResponse.owners;
} catch (err) {
log.error(`Could not query owner of NFT #${nft.tokenId}`);
}
if (insertToken(nft, contract._id)) {
numberOfTokensAdded += 1;
}
Expand Down
1 change: 1 addition & 0 deletions rair-node/bin/models/serverSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const ServerSetting = new Schema({
legal: { type: String, required: false },
// Favicon
favicon: { type: String, required: false },
signupMessage: { type: String, required: false, default: 'Welcome' },
}, { versionKey: false, timestamps: false });

module.exports = ServerSetting;
2 changes: 2 additions & 0 deletions rair-node/bin/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const nftController = require('../api/nft/nft.Controller');
const uploadController = require('../api/upload/upload.Controller');
const favoritesController = require('../api/favorites/favorites.Controller');
const notificationsController = require('../api/notifications/notifications.Controller');
const categoriesController = require('../api/categories/categories.Controller');

const router = Router();
router.use('/analytics', analyticsController);
Expand All @@ -36,6 +37,7 @@ router.use('/settings', settingsRouter);
router.use('/tokens', tokensController);
router.use('/upload', uploadController);
router.use('/notifications', notificationsController);
router.use('/categories', categoriesController);

// Custom temporary endpoint for the monaco2021
router.use('/', require('./monaco2021'));
Expand Down
7 changes: 7 additions & 0 deletions rair-node/bin/schemas/databaseSchemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
url: Joi.string(),
})),
legal: Joi.string(),
signupMessage: Joi.string(),
}),
dbContracts: () => ({
title: Joi.string(),
Expand Down Expand Up @@ -123,4 +124,10 @@ module.exports = {
type: Joi.string(),
read: Joi.boolean(),
}),
dbCategory: () => ({
list: Joi.array().items(Joi.object({
name: Joi.string().required(),
_id: mongoId,
})),
}),
};
19 changes: 11 additions & 8 deletions rair-node/bin/seeds/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ const { Blockchain, Category, ServerSetting, Contract } = require('../models');

module.exports = async () => {
try {
// eslint-disable-next-line no-restricted-syntax
for await (const category of categories) {
await Category.findOneAndUpdate(
category,
category,
{ upsert: true, new: true },
);
const count = await Category.estimatedDocumentCount();
if (count === 0) {
// eslint-disable-next-line no-restricted-syntax
for await (const category of categories) {
await Category.findOneAndUpdate(
category,
category,
{ upsert: true, new: true },
);
}
log.info('Categories empty, populating with default values.');
}
} catch (e) {
log.error(`Error seeding categories: ${e}`);
}
log.info('Categories seeded.');

try {
// eslint-disable-next-line no-restricted-syntax
Expand Down