diff --git a/package-lock.json b/package-lock.json index 13223b0..3e09d21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -665,6 +665,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "hashmap": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/hashmap/-/hashmap-2.4.0.tgz", + "integrity": "sha512-Ngj48lhnxJdnBAEVbubKBJuN1elfVLZJs94ZixRi98X3GCU4v6pgj9qRkHt6H8WaVJ69Wv0r1GhtS7hvF9zCgg==" + }, "hoek": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", diff --git a/package.json b/package.json index 781f8e0..6de9d7e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "cookie-parser": "^1.4.5", "express": "^4.17.1", "fs-extra": "^9.0.1", + "hashmap": "^2.4.0", "memory-cache": "^0.2.0", "moment": "^2.29.1", "mongoose": "^5.10.14", diff --git a/src/authentication.js b/src/authentication.js index 780c323..4bea657 100644 --- a/src/authentication.js +++ b/src/authentication.js @@ -2,15 +2,34 @@ const crypto = require('crypto'); const NodeRSA = require('node-rsa'); const fs = require('fs-extra'); const database = require('./database'); +const logger = require('./logger'); const config = require('./config.json'); const xmlParser = require('xml2json'); const request = require("request"); const moment = require('moment'); const { USER } = require('./models/user'); +var HashMap = require('hashmap'); let TGA = require('tga'); let pako = require('pako'); let PNG = require('pngjs').PNG; +let communityMap = new HashMap(); + +database.connect().then(async e => { + let communities = await database.getCommunities(); + if(communities !== null) { + for(let i = 0; i < communities.length; i++ ) { + for(let j = 0; j < communities[i].title_id.length; j++) { + communityMap.set(communities[i].title_id[j], communities[i].name); + } + + } + } + logger.success('Created community index') +}).catch(error => { + logger.error(error); +}); + let methods = { create_user: function(pid, experience, notifications) { return new Promise(function(resolve, reject) { @@ -25,8 +44,6 @@ let methods = { }, function (error, response, body) { if (!error && response.statusCode === 200) { let xml = xmlParser.toJson(body, {object: true}); - //console.log(xml); - //console.log(xml.miis.mii.images.image[0].cached_url); const newUsr = { pid: pid, created_at: moment().format('YYYY-MM-DD HH:mm:SS'), @@ -40,7 +57,6 @@ let methods = { official: false }; const newUsrObj = new USER(newUsr); - //console.log(newUsrObj); newUsrObj.save(); resolve(newUsr); } @@ -211,5 +227,8 @@ let methods = { return hashed; }, + getCommunityHash: function() { + return communityMap; + } }; exports.data = methods; \ No newline at end of file diff --git a/src/database.js b/src/database.js index 8eb0ef7..b6723b7 100644 --- a/src/database.js +++ b/src/database.js @@ -120,6 +120,29 @@ async function getHotPostsByCommunity(community, numberOfPosts) { title_id: community.title_id }).sort({empathy_count: -1}).limit(numberOfPosts); } + +async function getNumberNewCommunityPostsByID(community, number) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).sort({ created_at: -1}).limit(number); +} + +async function getNumberPopularCommunityPostsByID(community, number) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).sort({ empathy_count: -1}).limit(number); +} + +async function getNumberVerifiedCommunityPostsByID(community, number) { + verifyConnected(); + return POST.find({ + title_id: community.title_id, + verified: true + }).sort({ created_at: -1}).limit(number); +} + async function getPostsByCommunity(community, numberOfPosts) { verifyConnected(); return POST.find({ @@ -138,10 +161,12 @@ async function getPostsByCommunityKey(community, numberOfPosts, search_key) { async function getNewPostsByCommunity(community, numberOfPosts) { verifyConnected(); return POST.find({ - title_id: community.title_ids + title_id: community.title_id }).sort({ created_at: -1 }).limit(numberOfPosts); } + + async function getUserPostsAfterTimestamp(post, numberOfPosts) { verifyConnected(); return POST.find({ @@ -150,6 +175,14 @@ async function getUserPostsAfterTimestamp(post, numberOfPosts) { }).limit(numberOfPosts); } +async function getCommunityPostsAfterTimestamp(post, numberOfPosts) { + verifyConnected(); + return POST.find({ + title_id: post.title_id, + created_at: { $lt: post.created_at } + }).limit(numberOfPosts); +} + async function getDiscoveryHosts() { verifyConnected(); return ENDPOINT.findOne({ @@ -197,6 +230,9 @@ module.exports = { getDiscoveryHosts, getPostsByCommunity, getHotPostsByCommunity, + getNumberNewCommunityPostsByID, + getNumberPopularCommunityPostsByID, + getNumberVerifiedCommunityPostsByID, getNewPostsByCommunity, getPostsByCommunityKey, getPostsByUserID, @@ -207,5 +243,6 @@ module.exports = { getUserByPID, getUserByUsername, getUserPostsAfterTimestamp, + getCommunityPostsAfterTimestamp, getServerConfig }; \ No newline at end of file diff --git a/src/models/post.js b/src/models/post.js index 65f94de..743faea 100644 --- a/src/models/post.js +++ b/src/models/post.js @@ -21,7 +21,7 @@ const PostSchema = new Schema({ country_id: Number, created_at: Date, feeling_id: Number, - id: Number, + id: String, is_autopost: { type: Number, default: 0 diff --git a/src/services/juxt-web/index.js b/src/services/juxt-web/index.js index 70d62cd..ada1964 100644 --- a/src/services/juxt-web/index.js +++ b/src/services/juxt-web/index.js @@ -27,7 +27,8 @@ router.use(subdomain('web_api.olv', admin)); portal.use('/titles/show', routes.PORTAL_SHOW); portal.use('/communities', routes.PORTAL_COMMUNITIES); portal.use('/users', routes.PORTAL_USER); -portal.use('/post', routes.PORTAL_POST); +portal.use('/posts', routes.PORTAL_POST); +portal.use('/activity-feed', routes.PORTAL_FEED); portal.use('/', routes.PORTAL_WEB); ctr.use('/titles/show', routes.CTR_SHOW); diff --git a/src/services/juxt-web/routes/admin/home.js b/src/services/juxt-web/routes/admin/home.js index b839828..5a04718 100644 --- a/src/services/juxt-web/routes/admin/home.js +++ b/src/services/juxt-web/routes/admin/home.js @@ -4,6 +4,7 @@ const database = require('../../../../database'); const util = require('../../../../authentication'); const config = require('../../../../config.json'); const request = require("request"); +var path = require('path'); const ejs = require('ejs'); var multer = require('multer'); var upload = multer({ dest: 'uploads/' }); @@ -53,6 +54,10 @@ router.get('/', upload.none(), function (req, res) { }); +router.get('/css/juxt.css', function (req, res) { + res.sendFile('css/juxt.css', {root: path.join(__dirname, '../../../../webfiles/admin/')}); +}); + router.get('/discovery', upload.none(), function (req, res) { database.connect().then(async e => { @@ -135,7 +140,7 @@ router.get('/communities', upload.none(), function (req, res) { }); -router.get('/posts', upload.none(), function (req, res) { +router.get('/audit', upload.none(), function (req, res) { database.connect().then(async e => { @@ -154,7 +159,7 @@ router.get('/posts', upload.none(), function (req, res) { return; } let user = await database.getUserByPID(pid); - res.render('admin_posts.ejs', { + res.render('admin_audit.ejs', { user: user, }); @@ -217,6 +222,48 @@ router.get('/communities/new', upload.none(), function (req, res) { }); +router.get('/communities/:communityID/edit', upload.none(), function (req, res) { + + database.connect().then(async e => { + //let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]); + if(req.cookies.token === null) + { + res.redirect('/login'); + return; + } + + let pid = util.data.processServiceToken(req.cookies.token); + //console.log(req.headers["x-nintendo-servicetoken"]); + if(pid === null) + { + res.redirect('/login'); + return; + } + let user = await database.getUserByPID(pid); + let community = await database.getCommunityByID(req.params.communityID.toString()); + res.render('admin_edit_community.ejs', { + user: user, + community: community, + }); + + }).catch(error => { + console.log(error); + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); + +}); + router.get('/communities/:communityID', upload.none(), function (req, res) { database.connect().then(async e => { @@ -235,10 +282,15 @@ router.get('/communities/:communityID', upload.none(), function (req, res) { return; } let user = await database.getUserByPID(pid); - let community = await database.getCommunityByID(req.params.communityID); - res.render('admin_edit_community.ejs', { - user: user, + let community = await database.getCommunityByID(req.params.communityID.toString()); + let newPosts = await database.getNewPostsByCommunity(community, 100); + let totalNumPosts = await database.getTotalPostsByCommunity(community); + + res.render('admin_community.ejs', { community: community, + newPosts: newPosts, + totalNumPosts: totalNumPosts, + user: user }); }).catch(error => { @@ -375,6 +427,7 @@ router.post('/login', upload.none(), function (req, res) { if(config.authorized_PNIDs.indexOf(user.pid) === -1) throw new Error('User is not authorized to access the application'); let password_hash = await util.data.nintendoPasswordHash(password, user.pid); + console.log(password_hash) await request.post({ url: "http://" + config.account_server_domain + "/v1/api/oauth20/access_token/generate", headers: { @@ -394,6 +447,7 @@ router.post('/login', upload.none(), function (req, res) { } else { + console.log(body) res.statusCode = 400; let response = { error_code: 400, diff --git a/src/services/juxt-web/routes/index.js b/src/services/juxt-web/routes/index.js index c09cb39..1ede2cc 100644 --- a/src/services/juxt-web/routes/index.js +++ b/src/services/juxt-web/routes/index.js @@ -4,6 +4,7 @@ module.exports = { PORTAL_COMMUNITIES: require('./portal/communities'), PORTAL_USER: require('./portal/userpage'), PORTAL_POST: require('./portal/posts'), + PORTAL_FEED: require('./portal/feed'), CTR_SHOW: require('./ctr/show'), CTR_WEB: require('./ctr/web'), CTR_COMMUNITIES: require('./ctr/communities'), diff --git a/src/services/juxt-web/routes/portal/communities.js b/src/services/juxt-web/routes/portal/communities.js index 838a0ab..c4581b2 100644 --- a/src/services/juxt-web/routes/portal/communities.js +++ b/src/services/juxt-web/routes/portal/communities.js @@ -2,8 +2,8 @@ var express = require('express'); var xml = require('object-to-xml'); const database = require('../../../../database'); const util = require('../../../../authentication'); -const ejs = require('ejs'); var multer = require('multer'); +var moment = require('moment'); var upload = multer({ dest: 'uploads/' }); var router = express.Router(); @@ -54,11 +54,13 @@ router.get('/:communityID/new', function (req, res) { let user = await database.getUserByPID(pid); console.log(req.params.communityID) let community = await database.getCommunityByID(req.params.communityID.toString()); - let newPosts = await database.getNewPostsByCommunity(community, 100); + let newPosts = await database.getNumberNewCommunityPostsByID(community, 5); let totalNumPosts = await database.getTotalPostsByCommunity(community); + if(isAJAX) { res.render('portal_community_ajax.ejs', { // EJS variable and server-side variable + moment: moment, community: community, newPosts: newPosts, totalNumPosts: totalNumPosts, @@ -68,6 +70,7 @@ router.get('/:communityID/new', function (req, res) { else { res.render('portal_community.ejs', { // EJS variable and server-side variable + moment: moment, community: community, newPosts: newPosts, totalNumPosts: totalNumPosts, @@ -91,6 +94,64 @@ router.get('/:communityID/new', function (req, res) { }); }); +router.get('/:communityID/:type/loadPosts', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2|fonts.googleapis.com,,2|https,www.googletagmanager.com,,2'); + database.connect().then(async e => { + let pid = util.data.processServiceToken(req.headers["x-nintendo-servicetoken"]); + let post = await database.getPostByID(req.query.postID); + if(pid === null) + pid = 1000000000; + let user = await database.getUserByPID(pid); + let communityMap = await util.data.getCommunityHash(); + let posts; + if(post !== null) + posts = await database.getCommunityPostsAfterTimestamp(post, 1); + else { + let community = await database.getCommunityByID(req.params.communityID) + switch (req.params.type) { + case 'popular': + posts = await database.getNumberPopularCommunityPostsByID(community, 10); + break; + case 'verified': + posts = await database.getNumberVerifiedCommunityPostsByID(community, 10); + break; + default: + posts = await database.getNewPostsByCommunity(community, 10); + break; + } + } + + if(posts.length > 0) + { + res.render('portal_more_posts_ajax.ejs', { + communityMap: communityMap, + moment: moment, + database: database, + user: user, + newPosts: posts, + }); + } + else + { + res.sendStatus(204); + } + }).catch(error => { + console.log(error); + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + router.post('/follow', upload.none(), function (req, res) { database.connect().then(async e => { let pid = util.data.processServiceToken(req.headers["x-nintendo-servicetoken"]); diff --git a/src/services/juxt-web/routes/portal/feed.js b/src/services/juxt-web/routes/portal/feed.js new file mode 100644 index 0000000..8ad6d77 --- /dev/null +++ b/src/services/juxt-web/routes/portal/feed.js @@ -0,0 +1,93 @@ +var express = require('express'); +var xml = require('object-to-xml'); +const database = require('../../../../database'); +const util = require('../../../../authentication'); +var multer = require('multer'); +var moment = require('moment'); +var upload = multer({ dest: 'uploads/' }); +var router = express.Router(); + +router.get('/', function (req, res) { + res.sendStatus(404); +}); + +router.get('/loadPosts', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2|fonts.googleapis.com,,2|https,www.googletagmanager.com,,2'); + database.connect().then(async e => { + let pid = util.data.processServiceToken(req.headers["x-nintendo-servicetoken"]); + let post = await database.getPostByID(req.query.postID); + if(pid === null) + pid = 1000000000; + let user = await database.getUserByPID(pid); + let newPosts = await database.getUserPostsAfterTimestamp(post, 5); + let communityMap = await util.data.getCommunityHash(); + if(newPosts.length > 0) + { + res.render('portal_more_posts_ajax.ejs', { + communityMap: communityMap, + moment: moment, + user: user, + newPosts: newPosts, + }); + } + else + { + res.sendStatus(204); + } + }).catch(error => { + console.log(error); + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +router.post('/follow', upload.none(), function (req, res) { + database.connect().then(async e => { + let pid = util.data.processServiceToken(req.headers["x-nintendo-servicetoken"]); + let userToFollow = await database.getUserByPID(req.body.userID); + if(pid === null) + pid = 1000000000; + let user = await database.getUserByPID(pid); + if(req.body.type === 'true' && user !== null && user.followed_users.indexOf(userToFollow.pid) === -1) + { + userToFollow.addToFollowers(user.pid); + user.addToUsers(userToFollow.pid); + res.sendStatus(200); + } + else if(req.body.type === 'false' && user !== null && user.followed_users.indexOf(userToFollow.pid) !== -1) + { + userToFollow.removeFromFollowers(user.pid); + user.removeFromUsers(userToFollow.pid); + res.sendStatus(200); + } + else + res.sendStatus(423); + + }).catch(error => { + console.log(error); + res.set("Content-Type", "application/xml"); + res.statusCode = 423; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/portal/posts.js b/src/services/juxt-web/routes/portal/posts.js index c73940a..3b77dd7 100644 --- a/src/services/juxt-web/routes/portal/posts.js +++ b/src/services/juxt-web/routes/portal/posts.js @@ -3,11 +3,9 @@ var xml = require('object-to-xml'); const database = require('../../../../database'); const util = require('../../../../authentication'); const { POST } = require('../../../../models/post'); -const ejs = require('ejs'); var multer = require('multer'); var upload = multer({ dest: 'uploads/' }); const snowflake = require('node-snowflake').Snowflake; -const moment = require('moment'); var router = express.Router(); router.post('/empathy', function (req, res) { @@ -54,7 +52,6 @@ router.post('/new', upload.none(), async function (req, res, next) { { let paramPackData = util.data.decodeParamPack(req.headers["x-nintendo-parampack"]); let pid = util.data.processServiceToken(req.headers["x-nintendo-servicetoken"]); - //console.log(pid); if(pid === null) { throw new Error('The User token was not valid'); @@ -62,7 +59,8 @@ router.post('/new', upload.none(), async function (req, res, next) { else { let usrObj = await database.getUserByPID(pid); - const creationDate = moment().format('YYYY-MM-DD HH:MM:SS'); + //let community_id = req.body.olive_title_id.substring(0, req.body.olive_title_id.indexOf(',')); + let community = await database.getCommunityByID(req.body.olive_community_id); let appData = ""; if (req.body.app_data) { appData = req.body.app_data.replace(/\0/g, "").trim(); @@ -80,19 +78,16 @@ router.post('/new', upload.none(), async function (req, res, next) { screenshot = req.body.screenshot.replace(/\0/g, "").trim(); } const document = { - title_id: paramPackData.title_id, + title_id: community.title_id[0], screen_name: usrObj.user_id, body: req.body.body, app_data: appData, painting: painting, painting_uri: paintingURI, screenshot: screenshot, - url: req.body.url, - search_key: req.body.search_key, - topic_tag: req.body.topic_tag, community_id: req.body.community_id, country_id: paramPackData.country_id, - created_at: creationDate, + created_at: new Date(), feeling_id: req.body.feeling_id, id: snowflake.nextId(), is_autopost: req.body.is_autopost, @@ -107,7 +102,7 @@ router.post('/new', upload.none(), async function (req, res, next) { }; const newPost = new POST(document); newPost.save(); - res.sendStatus(200); + res.redirect('/communities/' + community.community_id + '/new'); } } catch (e) diff --git a/src/services/juxt-web/routes/portal/userpage.js b/src/services/juxt-web/routes/portal/userpage.js index 99c497f..35430b6 100644 --- a/src/services/juxt-web/routes/portal/userpage.js +++ b/src/services/juxt-web/routes/portal/userpage.js @@ -2,8 +2,8 @@ var express = require('express'); var xml = require('object-to-xml'); const database = require('../../../../database'); const util = require('../../../../authentication'); -const ejs = require('ejs'); var multer = require('multer'); +var moment = require('moment'); var upload = multer({ dest: 'uploads/' }); var router = express.Router(); @@ -19,9 +19,12 @@ router.get('/me', function (req, res) { let user = await database.getUserByPID(pid); let newPosts = await database.getNumberUserPostsByID(pid, 1); let numPosts = await database.getTotalPostsByUserID(pid); + let communityMap = await util.data.getCommunityHash(); if(isAJAX) { res.render('portal_me_page_ajax.ejs', { // EJS variable and server-side variable + communityMap: communityMap, + moment: moment, user: user, newPosts: newPosts, numPosts: numPosts @@ -30,6 +33,8 @@ router.get('/me', function (req, res) { else { res.render('portal_me_page.ejs', { // EJS variable and server-side variable + communityMap: communityMap, + moment: moment, user: user, newPosts: newPosts, numPosts: numPosts @@ -107,11 +112,16 @@ router.get('/show', function (req, res) { let parentUser = await database.getUserByPID(pid); let user = await database.getUserByPID(userID); - let newPosts = await database.getNumberUserPostsByID(user.pid, 1); + if(user === null) + res.sendStatus(404); + let newPosts = await database.getNumberUserPostsByID(user.pid, 10); let numPosts = await database.getTotalPostsByUserID(user.pid); + let communityMap = await util.data.getCommunityHash(); if(isAJAX) { res.render('portal_user_page_ajax.ejs', { // EJS variable and server-side variable + communityMap: communityMap, + moment: moment, user: user, newPosts: newPosts, numPosts: numPosts, @@ -121,9 +131,12 @@ router.get('/show', function (req, res) { else { res.render('portal_user_page.ejs', { // EJS variable and server-side variable + communityMap: communityMap, + moment: moment, user: user, newPosts: newPosts, - numPosts: numPosts + numPosts: numPosts, + parentUser: parentUser }); } }).catch(error => { @@ -151,14 +164,15 @@ router.get('/loadPosts', function (req, res) { if(pid === null) pid = 1000000000; let user = await database.getUserByPID(pid); - let newPosts = await database.getUserPostsAfterTimestamp(post, 1); - let numPosts = await database.getNumberUserPostsByID(pid); + let newPosts = await database.getUserPostsAfterTimestamp(post, 5); + let communityMap = await util.data.getCommunityHash(); if(newPosts.length > 0) { - res.render('portal_user_page_posts_ajax.ejs', { + res.render('portal_more_posts_ajax.ejs', { + communityMap: communityMap, + moment: moment, user: user, newPosts: newPosts, - numPosts: numPosts }); } else diff --git a/src/views/admin_communities.ejs b/src/views/admin_communities.ejs index f4f204e..6c461df 100644 --- a/src/views/admin_communities.ejs +++ b/src/views/admin_communities.ejs @@ -3,73 +3,18 @@