diff --git a/README.md b/README.md index 622f93b2..e5707e5e 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,13 @@ Start strapi: git clone https://github.com/rocketchat/RC4Community cd cms npm i -INITIALIZE_DATA=true npm run develop +INITIALIZE_DATA=true DISCOURSE_DOMAIN=domain DISCOURSE_API_USERNAME=username DISCOURSE_API_KEY=api_key npm run develop ``` -Note the `INITIALIZE_DATA` environment variable is only needed the first time you startup the cms for development. It will seed the cms with a default set of components for you to start your own customization. (see [fetch data](https://github.com/RonLek/RC4Community/blob/master/cms/config/functions/fetchData.js) for the actual default initialization code) +Note: +1. `INITIALIZE_DATA` environment variable is only needed the first time you startup the cms for development. It will seed the cms with a default set of components for you to start your own customization. (see [fetch data](https://github.com/RonLek/RC4Community/blob/master/cms/config/functions/fetchData.js) for the actual default initialization code) +2. `DISCOURSE_DOMAIN`, `DISCOURSE_API_USERNAME`, `DISCOURSE_API_KEY` environment variables are required for the cron job to fetch the latest top activity on discourse with the time interval of 5 mins. + The application is written on nextjs and deployable on all nextjs compatible CDN + microservices and scaled deployment platforms. For build and design, start it in a shell: diff --git a/app/pages/index.js b/app/pages/index.js index 9900968f..8bc66a6a 100644 --- a/app/pages/index.js +++ b/app/pages/index.js @@ -1,7 +1,6 @@ import React, { useState } from "react"; import { fetchAPI } from "../lib/api"; import { - Button, TextField, Select, MenuItem, @@ -31,50 +30,50 @@ export default function Home({ guides, releaseNotes, topNavItems, + topPosts }) { const [searchCategory, setSearchCategory] = useState(""); const { t } = useTranslation(); let loginWindow = null; - const activityItems = [ - { - title: - "I am setting up live chat and want to send an attachment ...how do I do that?", - author: "LigayaFernandez", - role: "LiveChat User", - community: "Question Forum", - time: "17 min ago", - upvotes: 0, - comments: 1, - }, - { - title: "Stranger Introduction", - author: "Izzie ", - role: "GSoC Student", - community: "GSoC 2021", - time: "1 hour ago", - upvotes: 5, - comments: 10, - }, - { - title: "Setting Up Rocket Chat", - author: "arary", - role: "Developer", - community: "Developer Discussions", - time: "2 hours ago", - upvotes: 0, - comments: 1, - }, - { - title: "RC4Community Improvements", - author: "aumurad", - role: "Admin", - community: "Announcements", - time: "4 hours ago", - upvotes: 50, - comments: 3, - }, - ]; + + function timeSince(date) { + let seconds = Math.floor((new Date() - date) / 1000); + let interval = seconds / 31536000; + if (interval > 1) { + return Math.floor(interval) + " years"; + } + interval = seconds / 2592000; + if (interval > 1) { + return Math.floor(interval) + " months ago"; + } + interval = seconds / 86400; + if (interval > 1) { + return Math.floor(interval) + " days ago"; + } + interval = seconds / 3600; + if (interval > 1) { + return Math.floor(interval) + " hours ago"; + } + interval = seconds / 60; + if (interval > 1) { + return Math.floor(interval) + " minutes ago"; + } + return Math.floor(seconds) + " seconds ago"; + } + let activityItems = []; + topPosts[0]?.TopPost?.topic_list?.topics.map((topic) => { + let newTopic = { + title: topic.fancy_title, + time: timeSince(new Date(topic.created_at)), + upvotes: topic.like_count, + comments: topic.posts_count, + link: `https://forums.rocket.chat/t/${topic.slug}/${topic.id}`, + image_url: topic.image_url + } + activityItems.push(newTopic); + }) + const Item = (props) => { return ( @@ -283,20 +282,16 @@ export default function Home({

{t("unsigned-home-demo.community-activity-heading")}

{activityItems.map((item) => ( -
+

{item.title}

- by{" "} - - {item.author}({item.role}) - {" "} - in {item.community} {item.time}{" "} + {item.time}{" "}

@@ -320,7 +315,7 @@ export default function Home({ {item.comments}
- + ))} @@ -334,9 +329,10 @@ export async function getStaticProps({ params }) { const guides = await fetchAPI("/guides"); const releaseNotes = await fetchAPI("/release-notes"); const topNavItems = await fetchAPI("/top-nav-item"); + const topPosts = await fetchAPI("/discourses"); return { - props: { carousels, personas, guides, releaseNotes, topNavItems }, + props: { carousels, personas, guides, releaseNotes, topNavItems, topPosts }, // Next.js will attempt to re-generate the page: // - When a request comes in // - At most once every 1 second diff --git a/cms/api/discourse/config/routes.json b/cms/api/discourse/config/routes.json new file mode 100644 index 00000000..c1828d63 --- /dev/null +++ b/cms/api/discourse/config/routes.json @@ -0,0 +1,52 @@ +{ + "routes": [ + { + "method": "GET", + "path": "/discourses", + "handler": "discourse.find", + "config": { + "policies": [] + } + }, + { + "method": "GET", + "path": "/discourses/count", + "handler": "discourse.count", + "config": { + "policies": [] + } + }, + { + "method": "GET", + "path": "/discourses/:id", + "handler": "discourse.findOne", + "config": { + "policies": [] + } + }, + { + "method": "POST", + "path": "/discourses", + "handler": "discourse.create", + "config": { + "policies": [] + } + }, + { + "method": "PUT", + "path": "/discourses/:id", + "handler": "discourse.update", + "config": { + "policies": [] + } + }, + { + "method": "DELETE", + "path": "/discourses/:id", + "handler": "discourse.delete", + "config": { + "policies": [] + } + } + ] +} diff --git a/cms/api/discourse/controllers/discourse.js b/cms/api/discourse/controllers/discourse.js new file mode 100644 index 00000000..e8608953 --- /dev/null +++ b/cms/api/discourse/controllers/discourse.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers) + * to customize this controller + */ + +module.exports = {}; diff --git a/cms/api/discourse/models/discourse.js b/cms/api/discourse/models/discourse.js new file mode 100644 index 00000000..0054d33c --- /dev/null +++ b/cms/api/discourse/models/discourse.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks) + * to customize this model + */ + +module.exports = {}; diff --git a/cms/api/discourse/models/discourse.settings.json b/cms/api/discourse/models/discourse.settings.json new file mode 100644 index 00000000..0ad4a21e --- /dev/null +++ b/cms/api/discourse/models/discourse.settings.json @@ -0,0 +1,21 @@ +{ + "kind": "collectionType", + "collectionName": "discourses", + "info": { + "name": "discourse", + "description": "" + }, + "options": { + "increments": true, + "timestamps": true, + "draftAndPublish": true + }, + "pluginOptions": {}, + "attributes": { + "TopPost": { + "type": "json", + "required": true, + "unique": false + } + } +} diff --git a/cms/api/discourse/services/discourse.js b/cms/api/discourse/services/discourse.js new file mode 100644 index 00000000..6538a8c8 --- /dev/null +++ b/cms/api/discourse/services/discourse.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services) + * to customize this service + */ + +module.exports = {}; diff --git a/cms/config/functions/bootstrap.js b/cms/config/functions/bootstrap.js index 51490ed4..47aaf4e1 100644 --- a/cms/config/functions/bootstrap.js +++ b/cms/config/functions/bootstrap.js @@ -1,5 +1,7 @@ "use strict"; +const { getLatestCommunityActivity } = require("./fetchTopPosts"); + /** * An asynchronous bootstrap function that runs before * your application gets started. @@ -32,5 +34,6 @@ module.exports = async () => { // Fetches data and populates CMS from remote on server restart if (process.env.INITIALIZE_DATA) { await strapi.config.functions.fetchData(); + await getLatestCommunityActivity(); } }; diff --git a/cms/config/functions/cron.js b/cms/config/functions/cron.js index fe26854c..0f90c276 100644 --- a/cms/config/functions/cron.js +++ b/cms/config/functions/cron.js @@ -1,5 +1,5 @@ 'use strict'; - +const { getLatestCommunityActivity } = require("./fetchTopPosts"); /** * Cron config that gives you an opportunity * to run scheduled jobs. @@ -18,4 +18,7 @@ module.exports = { // '0 1 * * 1': () => { // // } + '*/5 * * * *': () => { + getLatestCommunityActivity(); + } }; diff --git a/cms/config/functions/fetchTopPosts.js b/cms/config/functions/fetchTopPosts.js new file mode 100644 index 00000000..f46f08dd --- /dev/null +++ b/cms/config/functions/fetchTopPosts.js @@ -0,0 +1,25 @@ +const axios = require("axios"); + +module.exports.getLatestCommunityActivity = async () => { + const TopPost = await axios({ + url: `${process.env.DISCOURSE_DOMAIN}/top.json?period=all`, + method: "GET", + headers: { + "Api-Username": process.env.DISCOURSE_API_USERNAME, + "Api-Key": process.env.DISCOURSE_API_KEY, + }, + }); + let currentTopPost = await strapi.query("discourse").find(); + if (currentTopPost.length !== 0) { + await strapi.query("discourse").update( + { id: currentTopPost[0].id }, + { + TopPost: TopPost.data, + } + ); + } else { + await strapi.query("discourse").create({ + TopPost: TopPost.data, + }); + } +}; diff --git a/cms/config/server.js b/cms/config/server.js index ce7f7bdb..59707ac2 100644 --- a/cms/config/server.js +++ b/cms/config/server.js @@ -6,4 +6,7 @@ module.exports = ({ env }) => ({ secret: env('ADMIN_JWT_SECRET', '9e07dfd3396d88a43609466615259199'), }, }, + cron: { + enabled: true, + }, });