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

wordpress service + event-emitters #487

Merged
merged 1 commit into from
Nov 22, 2023
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
1 change: 1 addition & 0 deletions config/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const appConfig = {
callback: process.env.CALLBACK || 'callback',
},
apiUrl: process.env.API_URL || 'https://api.faforever.com',
wordpressUrl: process.env.WP_URL || 'https://direct.faforever.com',
extractorInterval: process.env.EXTRACTOR_INTERVAL || 5,
playerCountInterval: process.env.PLAYER_COUNT_INTERVAL || 15
}
Expand Down
3 changes: 3 additions & 0 deletions express.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ const newsRouter = require('./routes/views/news');
const staticMarkdownRouter = require('./routes/views/staticMarkdownRouter');
const leaderboardRouter = require('./routes/views/leaderboardRouter');
const authRouter = require('./routes/views/auth');
const dataRouter = require('./routes/views/dataRouter');

app.locals.clanInvitations = {};

//Execute Middleware
app.use(middleware.injectServices);
app.use(middleware.initLocals);
app.use(middleware.clientChecks);

Expand Down Expand Up @@ -74,6 +76,7 @@ app.use('/', authRouter)
app.use('/', staticMarkdownRouter)
app.use('/news', newsRouter)
app.use('/leaderboards', leaderboardRouter)
app.use('/data', dataRouter)

// --- UNPROTECTED ROUTES ---
const appGetRouteArray = [
Expand Down
1 change: 0 additions & 1 deletion grunt/nodemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ module.exports = {
'node_modules/**',
'grunt/**',
'Gruntfile.js',
'public/js/app/members/**'
]
}
}
Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
setupFilesAfterEnv: ['./tests/setup.js'],
};

module.exports = config;
9 changes: 9 additions & 0 deletions lib/CacheService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const NodeCache = require("node-cache");
const cacheService = new NodeCache(
{
stdTTL: 300, // use 5 min for all caches if not changed with ttl
checkperiod: 600 // cleanup memory every 10 min
}
)

module.exports = cacheService
5 changes: 3 additions & 2 deletions lib/LeaderboardService.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const {MutexService} = require("./MutexService");
const leaderboardTTL = 60 * 60 // 1 hours ttl as a relaxation for https://github.com/FAForever/website/issues/482

class LeaderboardService {
constructor(cacheService, mutexService, leaderboardRepository, lockTimeout = 3000) {
constructor(cacheService, leaderboardRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.mutexService = mutexService
this.mutexService = new MutexService()
this.leaderboardRepository = leaderboardRepository
}

Expand Down
13 changes: 2 additions & 11 deletions lib/LeaderboardServiceFactory.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
const LeaderboardService = require("./LeaderboardService");
const LeaderboardRepository = require("./LeaderboardRepository");
const {MutexService} = require("./MutexService");
const NodeCache = require("node-cache");
const {Axios} = require("axios");

const leaderboardMutex = new MutexService()
const cacheService = new NodeCache(
{
stdTTL: 300, // use 5 min for all caches if not changed with ttl
checkperiod: 600 // cleanup memory every 10 min
}
);
const cacheService = require('./CacheService')

module.exports = (javaApiBaseURL, token) => {
const config = {
Expand All @@ -19,5 +10,5 @@ module.exports = (javaApiBaseURL, token) => {
};
const javaApiClient = new Axios(config)

return new LeaderboardService(cacheService, leaderboardMutex, new LeaderboardRepository(javaApiClient))
return new LeaderboardService(cacheService, new LeaderboardRepository(javaApiClient))
}
18 changes: 18 additions & 0 deletions lib/Scheduler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const {EventEmitter} = require("events")

module.exports = class Scheduler extends EventEmitter {
constructor(eventName, action, ms) {
super()
this.eventName = eventName
this.action = action
this.handle = undefined
this.interval = ms
this.addListener(this.eventName, this.action);
}

start() {
if (!this.handle) {
this.handle = setInterval(() => this.emit(this.eventName), this.interval);
}
}
}
103 changes: 103 additions & 0 deletions lib/WordpressRepository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const {convert} = require("url-slug");

class WordpressRepository {
constructor(wordpressClient) {
this.wordpressClient = wordpressClient
}
async fetchNews() {
let response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=100&_embed&_fields=_links.author,_links.wp:featuredmedia,_embedded,title,content.rendered,date,categories&categories=587')
Copy link
Member

@Brutus5000 Brutus5000 Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the categories url was configurable via env?
But I see so many categories now, I'm no longer sure.
Maybe we should at least put the ids as const variables at the top of the file for better naming in the actual urls?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was just copy&pasta, https://github.com/FAForever/website/pull/487/files#diff-7e4783341040a4ba6619cd0157ccb8d361c450d4ac84148a2fe589414db924ddL22

maybe we can optimize it later? there are for sure other problems, like "options/fields" not used...


if (response.status !== 200) {
throw new Error('WordpressRepository::fetchNews failed with response status "' + response.status + '"')
}

const rawNewsData = JSON.parse(response.data)

if (typeof rawNewsData !== 'object' || rawNewsData === null) {
throw new Error('WordpressRepository::mapNewsResponse malformed response, not an object')
}

return rawNewsData.map(item => ({
slug: convert(item.title.rendered),
bcSlug: item.title.rendered.replace(/ /g, '-'),
date: item.date,
title: item.title.rendered,
content: item.content.rendered,
author: item._embedded.author[0].name,
media: item._embedded['wp:featuredmedia'][0].source_url,
}))
}

async fetchTournamentNews() {
let response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=10&_embed&_fields=content.rendered,categories&categories=638')

if (response.status !== 200) {
throw new Error('WordpressRepository::fetchTournamentNews failed with response status "' + response.status + '"')
}

let dataObjectToArray = JSON.parse(response.data);

let sortedData = dataObjectToArray.map(item => ({
content: item.content.rendered,
category: item.categories
}));

return sortedData.filter(article => article.category[1] !== 284)
}

async fetchContentCreators() {
const response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=100&_embed&_fields=content.rendered,categories&categories=639')

if (response.status !== 200) {
throw new Error('WordpressRepository::fetchContentCreators failed with response status "' + response.status + '"')
}

const items = JSON.parse(response.data);

return items.map(item => ({
content: item.content.rendered,
}))
}

async fetchFafTeams() {
const response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=100&_embed&_fields=content.rendered,categories&categories=636')

if (response.status !== 200) {
throw new Error('WordpressRepository::fetchFafTeams failed with response status "' + response.status + '"')
}

const items = JSON.parse(response.data);

return items.map(item => ({
content: item.content.rendered,
}))
}

async fetchNewshub() {
const response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=10&_embed&_fields=_links.author,_links.wp:featuredmedia,_embedded,title,newshub_externalLinkUrl,newshub_sortIndex,content.rendered,date,categories&categories=283')

if (response.status !== 200) {
throw new Error('WordpressRepository::fetchNewshub failed with response status "' + response.status + '"')
}

const items = JSON.parse(response.data);
const sortedData = items.map(item => ({
category: item.categories,
sortIndex: item.newshub_sortIndex,
link: item.newshub_externalLinkUrl,
date: item.date,
title: item.title.rendered,
content: item.content.rendered,
author: item._embedded.author[0].name,
media: item._embedded['wp:featuredmedia'][0].source_url,
}));

sortedData.sort((articleA, articleB) => articleB.sortIndex - articleA.sortIndex);

return sortedData.filter((article) => {
return article.category[1] !== 284;
})
}
}

module.exports = WordpressRepository
128 changes: 128 additions & 0 deletions lib/WordpressService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const {MutexService} = require("./MutexService");
const wordpressTTL = 60 * 60

class WordpressService {
constructor(cacheService, wordpressRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.mutexServices = {
news: new MutexService(),
tournament: new MutexService(),
creators: new MutexService(),
teams: new MutexService(),
newshub: new MutexService(),
}
this.wordpressRepository = wordpressRepository
}

getCacheKey(name) {
return 'WordpressService_' + name
}

async getNews(ignoreCache = false) {
const cacheKey = this.getCacheKey('news')

if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

if (this.mutexServices.news.locked) {
await this.mutexServices.news.acquire(() => {
}, this.lockTimeout)
return this.getNews()
}

await this.mutexServices.news.acquire(async () => {
const result = await this.wordpressRepository.fetchNews()
this.cacheService.set(cacheKey, result, wordpressTTL);
})

return this.getNews()
}

async getTournamentNews(ignoreCache = false) {
const cacheKey = this.getCacheKey('tournament-news')

if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

if (this.mutexServices.tournament.locked) {
await this.mutexService.acquire(() => {
}, this.lockTimeout)
return this.getTournamentNews()
}

await this.mutexServices.tournament.acquire(async () => {
const result = await this.wordpressRepository.fetchTournamentNews()
this.cacheService.set(cacheKey, result, wordpressTTL);
})

return this.getTournamentNews()
}

async getContentCreators(ignoreCache = false) {
const cacheKey = this.getCacheKey('content-creators')

if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

if (this.mutexServices.creators.locked) {
await this.mutexServices.creators.acquire(() => {
}, this.lockTimeout)
return this.getContentCreators()
}

await this.mutexServices.creators.acquire(async () => {
const result = await this.wordpressRepository.fetchContentCreators()
this.cacheService.set(cacheKey, result, wordpressTTL);
})

return this.getContentCreators()
}

async getFafTeams(ignoreCache = false) {
const cacheKey = this.getCacheKey('faf-teams')

if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

if (this.mutexServices.teams.locked) {
await this.mutexService.acquire(() => {
}, this.lockTimeout)
return this.getFafTeams()
}

await this.mutexServices.teams.acquire(async () => {
const result = await this.wordpressRepository.fetchFafTeams()
this.cacheService.set(cacheKey, result, wordpressTTL);
})

return this.getFafTeams()
}

async getNewshub(ignoreCache = false) {
const cacheKey = this.getCacheKey('newshub')

if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

if (this.mutexServices.newshub.locked) {
await this.mutexService.acquire(() => {
}, this.lockTimeout)
return this.getNewshub()
}

await this.mutexServices.newshub.acquire(async () => {
const result = await this.wordpressRepository.fetchNewshub()
this.cacheService.set(cacheKey, result, wordpressTTL);
})

return this.getNewshub()
}
}

module.exports = WordpressService
13 changes: 13 additions & 0 deletions lib/WordpressServiceFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const WordpressService = require("./WordpressService")
const WordpressRepository = require("./WordpressRepository")
const {Axios} = require("axios")
const cacheService = require('./CacheService')

module.exports = (wordpressBaseURL) => {
const config = {
baseURL: wordpressBaseURL
};
const wordpressClient = new Axios(config)

return new WordpressService(cacheService, new WordpressRepository(wordpressClient))
}
2 changes: 1 addition & 1 deletion public/js/app/content-creators.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
async function getWordpress() {
const response = await fetch(`./js/app/members/content-creators.json`);
const response = await fetch('/data/content-creators.json');
const data = await response.json();
let insertWordpress = document.getElementById('contentCreatorWordpress');
insertWordpress.insertAdjacentHTML('beforeend', `${data[0].content}`);
Expand Down
2 changes: 1 addition & 1 deletion public/js/app/faf-teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let teamSelection = document.querySelectorAll('.teamSelection');
let teamContainer = document.querySelectorAll('.teamContainer');
async function getWordpress() {
const response = await fetch(`js/app/members/faf-teams.json`);
const response = await fetch('/data/faf-teams.json');
const data = await response.json();
let insertWordpress = document.getElementById('insertWordpress');
insertWordpress.insertAdjacentHTML('beforeend', `${data[0].content}`);
Expand Down
1 change: 0 additions & 1 deletion public/js/app/members/.gitignore

This file was deleted.

6 changes: 2 additions & 4 deletions public/js/app/newshub.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

async function getNewshub() {
const response = await fetch(`js/app/members/newshub.json`);
const response = await fetch('/data/newshub.json');
const data = await response.json();
return await data;
}
async function getTournament() {
const response = await fetch(`js/app/members/tournament-news.json`);
const response = await fetch('/data/tournament-news.json');
const data = await response.json();
return await data;
}
Expand Down Expand Up @@ -72,8 +72,6 @@ let arrowRight = document.getElementById('clientArrowRigth');
let arrowLeft = document.getElementById('clientArrowLeft');
let newsPosition = 0;
let newsLimit = 0;
let newsMove = clientContainer[0].offsetWidth;
console.log(newsMove);
let spawnStyle = getComputedStyle(clientSpawn).columnGap;
let columnGap = spawnStyle.slice(0, 2);

Expand Down
Loading