diff --git a/.eslintrc b/.eslintrc
index e3803ff1..fed6689f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,7 +1,33 @@
{
- "root": true,
- "extends": ["standard"],
- "rules": {
- "indent": ["error", 4]
- }
-}
\ No newline at end of file
+ "root": true,
+ "extends": ["standard", "plugin:prettier/recommended"],
+ "plugins": ["prettier"],
+ "rules": {
+ "prettier/prettier": "error"
+ },
+ "overrides": [
+ {
+ "files": ["src/frontend/**/*.js"],
+ "env": {
+ "browser": true,
+ "es2020": true
+ }
+ },
+ {
+ "files": ["src/backend/**/*.js"],
+ "env": {
+ "node": true,
+ "es2020": true
+ }
+ },
+ {
+ "files": ["tests/**/*.js"],
+ "env": {
+ "es2020": true,
+ "node": true,
+ "jest": true,
+ "jasmine": true
+ }
+ }
+ ]
+}
diff --git a/.prettierrc.yaml b/.prettierrc.yaml
new file mode 100644
index 00000000..d982b805
--- /dev/null
+++ b/.prettierrc.yaml
@@ -0,0 +1,16 @@
+# See https://prettier.io/docs/en/options. most are set to default
+trailingComma: "es5"
+tabWidth: 4
+semi: false
+singleQuote: true
+bracketSpacing: true
+bracketSameLine: false
+arrowParens: always
+htmlWhitespaceSensitivity: strict
+singleAttributePerLine: false
+plugins:
+ - "@prettier/plugin-pug"
+overrides:
+ - files: "*.pug"
+ options:
+ parser: "pug"
\ No newline at end of file
diff --git a/package.json b/package.json
index 0dd0ec29..575dab82 100644
--- a/package.json
+++ b/package.json
@@ -30,13 +30,16 @@
"url-slug": "^4.0.1"
},
"devDependencies": {
+ "@prettier/plugin-pug": "^3.0.0",
"awesomplete": "^1.1.5",
"css-loader": "^6.8.1",
"dart-sass": "^1.25.0",
"eslint": "^8.54.0",
+ "eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^16.3.1",
+ "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-promise": "^6.1.1",
"grunt": "1.6.1",
"grunt-concurrent": "3.0.0",
@@ -51,6 +54,7 @@
"load-grunt-tasks": "5.1.0",
"nock": "^13.3.8",
"octokit": "^3.1.2",
+ "prettier": "^3.1.1",
"style-loader": "^3.3.3",
"supertest": "^6.3.3",
"typescript": "^5.3.2",
@@ -64,7 +68,7 @@
},
"scripts": {
"test": "jest",
- "lint": "eslint --ignore-path .gitignore src tests",
- "lint:fix": "eslint --fix --ignore-path .gitignore src tests"
+ "lint": "eslint --ignore-path .gitignore src tests && prettier --check **/*.pug",
+ "lint:fix": "eslint --fix --ignore-path .gitignore src tests && prettier --write **/*.pug"
}
}
diff --git a/src/backend/.eslintrc b/src/backend/.eslintrc
deleted file mode 100644
index 215902c1..00000000
--- a/src/backend/.eslintrc
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "env": {
- "node": true,
- "es2020": true
- }
-}
diff --git a/src/backend/AppKernel.js b/src/backend/AppKernel.js
index bafc21ac..4d3406d0 100644
--- a/src/backend/AppKernel.js
+++ b/src/backend/AppKernel.js
@@ -1,6 +1,8 @@
const { appContainer } = require('./dependency-injection/AppContainer')
const { RequestContainer } = require('./dependency-injection/RequestContainer')
-const { RequestContainerCompilerPass } = require('./dependency-injection/RequestContainerCompilerPass')
+const {
+ RequestContainerCompilerPass,
+} = require('./dependency-injection/RequestContainerCompilerPass')
const { webpackAsset } = require('./middleware/webpackAsset')
const { bootPassport } = require('./security/bootPassport')
const express = require('./ExpressApp')
@@ -22,7 +24,7 @@ const accountRouter = require('./routes/views/accountRouter')
const dataRouter = require('./routes/views/dataRouter')
class AppKernel {
- constructor (nodeEnv = 'production') {
+ constructor(nodeEnv = 'production') {
this.env = nodeEnv
this.config = appConfig
this.expressApp = null
@@ -30,18 +32,18 @@ class AppKernel {
this.schedulers = []
}
- async boot () {
+ async boot() {
await this.compileContainer(this.config)
this.bootstrapExpress()
return this
}
- async compileContainer (config) {
+ async compileContainer(config) {
this.appContainer = appContainer(config)
await this.appContainer.compile()
}
- bootstrapExpress () {
+ bootstrapExpress() {
this.expressApp = express()
this.expressApp.locals.clanInvitations = {}
@@ -49,44 +51,55 @@ class AppKernel {
res.locals.navLinks = []
res.locals.cNavLinks = []
res.locals.appGlobals = {
- loggedInUser: null
+ loggedInUser: null,
}
next()
})
this.expressApp.set('views', 'src/backend/templates/views')
this.expressApp.set('view engine', 'pug')
- this.expressApp.use(express.static('public', {
- immutable: true,
- maxAge: 4 * 60 * 60 * 1000 // 4 hours
- }))
+ this.expressApp.use(
+ express.static('public', {
+ immutable: true,
+ maxAge: 4 * 60 * 60 * 1000, // 4 hours
+ })
+ )
- this.expressApp.use('/dist', express.static('dist', {
- immutable: true,
- maxAge: 4 * 60 * 60 * 1000 // 4 hours, could be longer since we got cache-busting
- }))
+ this.expressApp.use(
+ '/dist',
+ express.static('dist', {
+ immutable: true,
+ maxAge: 4 * 60 * 60 * 1000, // 4 hours, could be longer since we got cache-busting
+ })
+ )
this.expressApp.use(express.json())
this.expressApp.use(bodyParser.json())
this.expressApp.use(bodyParser.urlencoded({ extended: false }))
- this.expressApp.use(webpackAsset(this.appContainer.getParameter('webpackManifestJS')))
-
- this.expressApp.use(session({
- resave: false,
- saveUninitialized: true,
- secret: appConfig.session.key,
- store: new FileStore({
- retries: 0,
- ttl: appConfig.session.tokenLifespan,
- secret: appConfig.session.key
+ this.expressApp.use(
+ webpackAsset(this.appContainer.getParameter('webpackManifestJS'))
+ )
+
+ this.expressApp.use(
+ session({
+ resave: false,
+ saveUninitialized: true,
+ secret: appConfig.session.key,
+ store: new FileStore({
+ retries: 0,
+ ttl: appConfig.session.tokenLifespan,
+ secret: appConfig.session.key,
+ }),
})
- }))
+ )
bootPassport(this.expressApp, this.config)
this.expressApp.use(async (req, res, next) => {
req.appContainer = this.appContainer
req.requestContainer = RequestContainer(this.appContainer, req)
- req.requestContainer.addCompilerPass(new RequestContainerCompilerPass(this.config, req))
+ req.requestContainer.addCompilerPass(
+ new RequestContainerCompilerPass(this.config, req)
+ )
await req.requestContainer.compile()
if (req.requestContainer.fafThrownException) {
@@ -100,7 +113,7 @@ class AppKernel {
this.expressApp.use(function (req, res, next) {
req.asyncFlash = async function () {
const result = req.flash(...arguments)
- await new Promise(resolve => req.session.save(resolve))
+ await new Promise((resolve) => req.session.save(resolve))
return result
}
@@ -114,19 +127,27 @@ class AppKernel {
this.expressApp.use(function (req, res, next) {
if (req.isAuthenticated()) {
- res.locals.appGlobals.loggedInUser = req.requestContainer.get('UserService').getUser()
+ res.locals.appGlobals.loggedInUser = req.requestContainer
+ .get('UserService')
+ .getUser()
}
next()
})
}
- startCronJobs () {
- this.schedulers.push(leaderboardCacheCrawler(this.appContainer.get('LeaderboardService')))
- this.schedulers.push(wordpressCacheCrawler(this.appContainer.get('WordpressService')))
- this.schedulers.push(clanCacheCrawler(this.appContainer.get('ClanService')))
+ startCronJobs() {
+ this.schedulers.push(
+ leaderboardCacheCrawler(this.appContainer.get('LeaderboardService'))
+ )
+ this.schedulers.push(
+ wordpressCacheCrawler(this.appContainer.get('WordpressService'))
+ )
+ this.schedulers.push(
+ clanCacheCrawler(this.appContainer.get('ClanService'))
+ )
}
- loadControllers () {
+ loadControllers() {
this.expressApp.use('/', defaultRouter)
this.expressApp.use('/', authRouter)
this.expressApp.use('/', staticMarkdownRouter)
@@ -140,7 +161,13 @@ class AppKernel {
res.status(404).render('errors/404')
})
this.expressApp.use((err, req, res, next) => {
- console.error('[error] Incoming request to"', req.originalUrl, '"failed with error "', err.toString(), '"')
+ console.error(
+ '[error] Incoming request to"',
+ req.originalUrl,
+ '"failed with error "',
+ err.toString(),
+ '"'
+ )
console.error(err.stack)
if (res.headersSent) {
diff --git a/src/backend/config/app.js b/src/backend/config/app.js
index b5e3d021..7efb1777 100644
--- a/src/backend/config/app.js
+++ b/src/backend/config/app.js
@@ -8,7 +8,7 @@ const appConfig = {
host: process.env.HOST || 'http://localhost',
session: {
key: process.env.SESSION_SECRET_KEY || '12345',
- tokenLifespan: process.env.TOKEN_LIFESPAN || 43200
+ tokenLifespan: process.env.TOKEN_LIFESPAN || 43200,
},
oauth: {
strategy: 'faforever',
@@ -16,18 +16,18 @@ const appConfig = {
clientSecret: process.env.OAUTH_CLIENT_SECRET || '12345',
url: oauthUrl,
publicUrl: process.env.OAUTH_PUBLIC_URL || oauthUrl,
- callback: process.env.CALLBACK || 'callback'
+ callback: process.env.CALLBACK || 'callback',
},
m2mOauth: {
clientId: process.env.OAUTH_M2M_CLIENT_ID || 'faf-website-public',
clientSecret: process.env.OAUTH_M2M_CLIENT_SECRET || 'banana',
- url: oauthUrl
+ url: oauthUrl,
},
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,
- recaptchaKey: process.env.RECAPTCHA_SITE_KEY || 'test'
+ recaptchaKey: process.env.RECAPTCHA_SITE_KEY || 'test',
}
module.exports = appConfig
diff --git a/src/backend/cron-jobs/clanCacheCrawler.js b/src/backend/cron-jobs/clanCacheCrawler.js
index b1ae1e86..c0e20b9b 100644
--- a/src/backend/cron-jobs/clanCacheCrawler.js
+++ b/src/backend/cron-jobs/clanCacheCrawler.js
@@ -10,12 +10,17 @@ const errorHandler = (e, name) => {
const warmupClans = async (clanService) => {
try {
- await clanService.getAll(true)
+ await clanService
+ .getAll(true)
.then(() => successHandler('clanService::getAll(global)'))
.catch((e) => errorHandler(e, 'clanService::getAll(global)'))
} catch (e) {
- console.error('Error: clanCacheCrawler::warmupClans failed with "' + e.toString() + '"',
- { entrypoint: 'clanCacheCrawler.js' })
+ console.error(
+ 'Error: clanCacheCrawler::warmupClans failed with "' +
+ e.toString() +
+ '"',
+ { entrypoint: 'clanCacheCrawler.js' }
+ )
console.error(e.stack)
}
}
@@ -27,8 +32,11 @@ const warmupClans = async (clanService) => {
module.exports = (clanService) => {
warmupClans(clanService).then(() => {})
- const clansScheduler = new Scheduler('createClanCache', // Refresh cache every 59 minutes
- () => warmupClans(clanService).then(() => {}), 60 * 59 * 1000)
+ const clansScheduler = new Scheduler(
+ 'createClanCache', // Refresh cache every 59 minutes
+ () => warmupClans(clanService).then(() => {}),
+ 60 * 59 * 1000
+ )
clansScheduler.start()
return clansScheduler
diff --git a/src/backend/cron-jobs/leaderboardCacheCrawler.js b/src/backend/cron-jobs/leaderboardCacheCrawler.js
index 8e818a5b..6233fbd8 100644
--- a/src/backend/cron-jobs/leaderboardCacheCrawler.js
+++ b/src/backend/cron-jobs/leaderboardCacheCrawler.js
@@ -4,29 +4,57 @@ const successHandler = (name) => {
console.debug('[debug] Cache updated', { name })
}
const errorHandler = (e, name) => {
- console.error(e.toString(), { name, entrypoint: 'leaderboardCacheCrawler.js' })
+ console.error(e.toString(), {
+ name,
+ entrypoint: 'leaderboardCacheCrawler.js',
+ })
console.error(e.stack)
}
const warmupLeaderboard = async (leaderboardService) => {
try {
- await leaderboardService.getLeaderboard(1, true)
- .then(() => successHandler('leaderboardService::getLeaderboard(global)'))
- .catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(global)'))
+ await leaderboardService
+ .getLeaderboard(1, true)
+ .then(() =>
+ successHandler('leaderboardService::getLeaderboard(global)')
+ )
+ .catch((e) =>
+ errorHandler(e, 'leaderboardService::getLeaderboard(global)')
+ )
- await leaderboardService.getLeaderboard(2, true)
- .then(() => successHandler('leaderboardService::getLeaderboard(1v1)'))
- .catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(1v1)'))
+ await leaderboardService
+ .getLeaderboard(2, true)
+ .then(() =>
+ successHandler('leaderboardService::getLeaderboard(1v1)')
+ )
+ .catch((e) =>
+ errorHandler(e, 'leaderboardService::getLeaderboard(1v1)')
+ )
- await leaderboardService.getLeaderboard(3, true)
- .then(() => successHandler('leaderboardService::getLeaderboard(2v2)'))
- .catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(2v2)'))
+ await leaderboardService
+ .getLeaderboard(3, true)
+ .then(() =>
+ successHandler('leaderboardService::getLeaderboard(2v2)')
+ )
+ .catch((e) =>
+ errorHandler(e, 'leaderboardService::getLeaderboard(2v2)')
+ )
- await leaderboardService.getLeaderboard(4, true)
- .then(() => successHandler('leaderboardService::getLeaderboard(4v4)'))
- .catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(4v4)'))
+ await leaderboardService
+ .getLeaderboard(4, true)
+ .then(() =>
+ successHandler('leaderboardService::getLeaderboard(4v4)')
+ )
+ .catch((e) =>
+ errorHandler(e, 'leaderboardService::getLeaderboard(4v4)')
+ )
} catch (e) {
- console.error('Error: leaderboardCacheCrawler::warmupLeaderboard failed with "' + e.toString() + '"', { entrypoint: 'leaderboardCacheCrawler.js' })
+ console.error(
+ 'Error: leaderboardCacheCrawler::warmupLeaderboard failed with "' +
+ e.toString() +
+ '"',
+ { entrypoint: 'leaderboardCacheCrawler.js' }
+ )
console.error(e.stack)
}
}
@@ -38,8 +66,11 @@ const warmupLeaderboard = async (leaderboardService) => {
module.exports = (leaderboardService) => {
warmupLeaderboard(leaderboardService).then(() => {})
- const leaderboardScheduler = new Scheduler('createLeaderboardCaches',
- () => warmupLeaderboard(leaderboardService).then(() => {}), 60 * 59 * 1000)
+ const leaderboardScheduler = new Scheduler(
+ 'createLeaderboardCaches',
+ () => warmupLeaderboard(leaderboardService).then(() => {}),
+ 60 * 59 * 1000
+ )
leaderboardScheduler.start()
return leaderboardScheduler
diff --git a/src/backend/cron-jobs/wordpressCacheCrawler.js b/src/backend/cron-jobs/wordpressCacheCrawler.js
index abd5e7d1..15cf190f 100644
--- a/src/backend/cron-jobs/wordpressCacheCrawler.js
+++ b/src/backend/cron-jobs/wordpressCacheCrawler.js
@@ -4,33 +4,50 @@ const successHandler = (name) => {
console.debug('[debug] Cache updated', { name })
}
const errorHandler = (e, name) => {
- console.error(e.toString(), { name, entrypoint: 'wordpressCacheCrawler.js' })
+ console.error(e.toString(), {
+ name,
+ entrypoint: 'wordpressCacheCrawler.js',
+ })
console.error(e.stack)
}
const warmupWordpressCache = (wordpressService) => {
try {
- wordpressService.getNews(true)
+ wordpressService
+ .getNews(true)
.then(() => successHandler('wordpressService::getNews'))
.catch((e) => errorHandler(e, 'wordpressService::getNews'))
- wordpressService.getNewshub(true)
+ wordpressService
+ .getNewshub(true)
.then(() => successHandler('wordpressService::getNewshub'))
.catch((e) => errorHandler(e, 'wordpressService::getNewshub'))
- wordpressService.getContentCreators(true)
+ wordpressService
+ .getContentCreators(true)
.then(() => successHandler('wordpressService::getContentCreators'))
- .catch((e) => errorHandler(e, 'wordpressService::getContentCreators'))
+ .catch((e) =>
+ errorHandler(e, 'wordpressService::getContentCreators')
+ )
- wordpressService.getTournamentNews(true)
+ wordpressService
+ .getTournamentNews(true)
.then(() => successHandler('wordpressService::getTournamentNews'))
- .catch((e) => errorHandler(e, 'wordpressService::getTournamentNews'))
+ .catch((e) =>
+ errorHandler(e, 'wordpressService::getTournamentNews')
+ )
- wordpressService.getFafTeams(true)
+ wordpressService
+ .getFafTeams(true)
.then(() => successHandler('wordpressService::getFafTeams'))
.catch((e) => errorHandler(e, 'wordpressService::getFafTeams'))
} catch (e) {
- console.error('Error: wordpressCacheCrawler::warmupWordpressCache failed with "' + e.toString() + '"', { entrypoint: 'wordpressCacheCrawler.js' })
+ console.error(
+ 'Error: wordpressCacheCrawler::warmupWordpressCache failed with "' +
+ e.toString() +
+ '"',
+ { entrypoint: 'wordpressCacheCrawler.js' }
+ )
console.error(e.stack)
}
}
@@ -42,8 +59,11 @@ const warmupWordpressCache = (wordpressService) => {
module.exports = (wordpressService) => {
warmupWordpressCache(wordpressService)
- const wordpressScheduler = new Scheduler('createWordpressCaches',
- () => warmupWordpressCache(wordpressService), 60 * 59 * 1000)
+ const wordpressScheduler = new Scheduler(
+ 'createWordpressCaches',
+ () => warmupWordpressCache(wordpressService),
+ 60 * 59 * 1000
+ )
wordpressScheduler.start()
return wordpressScheduler
diff --git a/src/backend/dependency-injection/AppContainer.js b/src/backend/dependency-injection/AppContainer.js
index 1bf09d22..8148666d 100644
--- a/src/backend/dependency-injection/AppContainer.js
+++ b/src/backend/dependency-injection/AppContainer.js
@@ -9,7 +9,9 @@ const { ClanService } = require('../services/ClanService')
const NodeCache = require('node-cache')
const { Axios } = require('axios')
const fs = require('fs')
-const webpackManifestJS = JSON.parse(fs.readFileSync('dist/js/manifest.json', 'utf8'))
+const webpackManifestJS = JSON.parse(
+ fs.readFileSync('dist/js/manifest.json', 'utf8')
+)
/**
* @param {object} appConfig
@@ -20,42 +22,47 @@ module.exports.appContainer = function (appConfig) {
container.setParameter('webpackManifestJS', webpackManifestJS)
- container.register('NodeCache', NodeCache)
- .addArgument({
- stdTTL: 300, // use 5 min for all caches if not changed with ttl
- checkperiod: 600 // cleanup memory every 10 min
- })
+ container.register('NodeCache', NodeCache).addArgument({
+ stdTTL: 300, // use 5 min for all caches if not changed with ttl
+ checkperiod: 600, // cleanup memory every 10 min
+ })
- container.register('WordpressClient', Axios)
- .addArgument({
- baseURL: appConfig.wordpressUrl
- })
+ container.register('WordpressClient', Axios).addArgument({
+ baseURL: appConfig.wordpressUrl,
+ })
- container.register('WordpressRepository', WordpressRepository)
+ container
+ .register('WordpressRepository', WordpressRepository)
.addArgument(new Reference('WordpressClient'))
- container.register('WordpressService', WordpressService)
+ container
+ .register('WordpressService', WordpressService)
.addArgument(new Reference('NodeCache'))
.addArgument(new Reference('WordpressRepository'))
- container.register('JavaApiM2MClient')
+ container
+ .register('JavaApiM2MClient')
.addArgument(appConfig.m2mOauth.clientId)
.addArgument(appConfig.m2mOauth.clientSecret)
.addArgument(appConfig.m2mOauth.url)
.addArgument(appConfig.apiUrl)
.setFactory(JavaApiM2MClient, 'createInstance')
- container.register('LeaderboardRepository', LeaderboardRepository)
+ container
+ .register('LeaderboardRepository', LeaderboardRepository)
.addArgument(new Reference('JavaApiM2MClient'))
- container.register('LeaderboardService', LeaderboardService)
+ container
+ .register('LeaderboardService', LeaderboardService)
.addArgument(new Reference('NodeCache'))
.addArgument(new Reference('LeaderboardRepository'))
- container.register('DataRepository', DataRepository)
+ container
+ .register('DataRepository', DataRepository)
.addArgument(new Reference('JavaApiM2MClient'))
- container.register('ClanService', ClanService)
+ container
+ .register('ClanService', ClanService)
.addArgument(new Reference('NodeCache'))
.addArgument(new Reference('DataRepository'))
diff --git a/src/backend/dependency-injection/RequestContainer.js b/src/backend/dependency-injection/RequestContainer.js
index c0d52db4..794e94ed 100644
--- a/src/backend/dependency-injection/RequestContainer.js
+++ b/src/backend/dependency-injection/RequestContainer.js
@@ -2,37 +2,37 @@ const { ContainerBuilder, Reference } = require('node-dependency-injection')
const { UserRepository } = require('../services/UserRepository')
const { UserService } = require('../services/UserService')
const { ClanManagementService } = require('../services/ClanManagementService')
-const { ClanManagementRepository } = require('../services/ClanManagementRepository')
+const {
+ ClanManagementRepository,
+} = require('../services/ClanManagementRepository')
module.exports.RequestContainer = (appContainer, request) => {
const container = new ContainerBuilder()
container.setParameter('request', request)
- container.register('UserRepository', UserRepository)
- .addArgument(new Reference('JavaApiClient'))
- .lazy = true
+ container
+ .register('UserRepository', UserRepository)
+ .addArgument(new Reference('JavaApiClient')).lazy = true
- container.register('ClanManagementService', ClanManagementService)
+ container
+ .register('ClanManagementService', ClanManagementService)
.addArgument(new Reference('UserService'))
- .addArgument(new Reference('ClanManagementRepository'))
- .lazy = true
+ .addArgument(new Reference('ClanManagementRepository')).lazy = true
- container.register('ClanManagementRepository', ClanManagementRepository)
- .addArgument(new Reference('JavaApiClient'))
- .lazy = true
+ container
+ .register('ClanManagementRepository', ClanManagementRepository)
+ .addArgument(new Reference('JavaApiClient')).lazy = true
- container.register('ClanManagementService', ClanManagementService)
+ container
+ .register('ClanManagementService', ClanManagementService)
.addArgument(new Reference('UserService'))
.addArgument(new Reference('ClanManagementRepository'))
- .addArgument(appContainer.get('ClanService'))
- .lazy = true
+ .addArgument(appContainer.get('ClanService')).lazy = true
- container.register('JavaApiClient')
- .synthetic = true
+ container.register('JavaApiClient').synthetic = true
- container.register('UserService', UserService)
- .lazy = true
+ container.register('UserService', UserService).lazy = true
return container
}
diff --git a/src/backend/dependency-injection/RequestContainerCompilerPass.js b/src/backend/dependency-injection/RequestContainerCompilerPass.js
index 78c000bd..88a94217 100644
--- a/src/backend/dependency-injection/RequestContainerCompilerPass.js
+++ b/src/backend/dependency-injection/RequestContainerCompilerPass.js
@@ -1,17 +1,27 @@
const { JavaApiClientFactory } = require('../services/JavaApiClientFactory')
class RequestContainerCompilerPass {
- constructor (appConfig, request = null) {
+ constructor(appConfig, request = null) {
this.request = request
this.appConfig = appConfig
}
- async process (container) {
+ async process(container) {
try {
if (this.request.user) {
container.get('UserService').setUserFromRequest(this.request)
- container.set('JavaApiClient', JavaApiClientFactory.createInstance(container.get('UserService'), this.appConfig.apiUrl, this.request.user.oAuthPassport, this.appConfig.oauth.strategy))
- container.get('UserService').setUserRepository(container.get('UserRepository'))
+ container.set(
+ 'JavaApiClient',
+ JavaApiClientFactory.createInstance(
+ container.get('UserService'),
+ this.appConfig.apiUrl,
+ this.request.user.oAuthPassport,
+ this.appConfig.oauth.strategy
+ )
+ )
+ container
+ .get('UserService')
+ .setUserRepository(container.get('UserRepository'))
}
return container
diff --git a/src/backend/index.js b/src/backend/index.js
index bf8347eb..cabca9e2 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -1,11 +1,12 @@
const { AppKernel } = require('./AppKernel')
const kernel = new AppKernel()
-kernel.boot()
- .then((bootedKernel) => {
- bootedKernel.startCronJobs()
- bootedKernel.loadControllers()
- bootedKernel.expressApp.listen(bootedKernel.config.expressPort, () => {
- console.log(`Express listening on port ${bootedKernel.config.expressPort}`)
- })
+kernel.boot().then((bootedKernel) => {
+ bootedKernel.startCronJobs()
+ bootedKernel.loadControllers()
+ bootedKernel.expressApp.listen(bootedKernel.config.expressPort, () => {
+ console.log(
+ `Express listening on port ${bootedKernel.config.expressPort}`
+ )
})
+})
diff --git a/src/backend/middleware/webpackAsset.js b/src/backend/middleware/webpackAsset.js
index 8a33e54a..dceb682e 100644
--- a/src/backend/middleware/webpackAsset.js
+++ b/src/backend/middleware/webpackAsset.js
@@ -5,7 +5,11 @@ module.exports.webpackAsset = (webpackManifestJS) => {
return webpackManifestJS[asset]
}
- throw new Error('[error] middleware::webpackAsset Failed to find asset "' + asset + '"')
+ throw new Error(
+ '[error] middleware::webpackAsset Failed to find asset "' +
+ asset +
+ '"'
+ )
}
next()
}
diff --git a/src/backend/routes/middleware.js b/src/backend/routes/middleware.js
index cfd579b5..c8e91d0a 100755
--- a/src/backend/routes/middleware.js
+++ b/src/backend/routes/middleware.js
@@ -1,10 +1,17 @@
-exports.isAuthenticated = (redirectUrlAfterLogin = null, isApiRequest = false) => {
+exports.isAuthenticated = (
+ redirectUrlAfterLogin = null,
+ isApiRequest = false
+) => {
return (req, res, next) => {
if (req.isAuthenticated()) {
return next()
}
- if (req.xhr || req.headers?.accept?.indexOf('json') > -1 || isApiRequest) {
+ if (
+ req.xhr ||
+ req.headers?.accept?.indexOf('json') > -1 ||
+ isApiRequest
+ ) {
return res.status(401).json({ error: 'Unauthorized' })
}
diff --git a/src/backend/routes/views/account/get/checkUsername.js b/src/backend/routes/views/account/get/checkUsername.js
index 1d814e3c..9f11ec59 100644
--- a/src/backend/routes/views/account/get/checkUsername.js
+++ b/src/backend/routes/views/account/get/checkUsername.js
@@ -3,17 +3,20 @@ const request = require('request')
exports = module.exports = function (req, res) {
const name = req.query.username
- request(process.env.API_URL + '/data/player?filter=login==' + encodeURI(name), function (error, response, body) {
- if (error) {
- console.error(error)
- return res.status(500).send(error)
- }
+ request(
+ process.env.API_URL + '/data/player?filter=login==' + encodeURI(name),
+ function (error, response, body) {
+ if (error) {
+ console.error(error)
+ return res.status(500).send(error)
+ }
- try {
- const userNameFree = JSON.parse(body).data.length === 0
- return res.status(userNameFree ? 200 : 400).send(userNameFree)
- } catch (e) {
- return res.status(500).send(e)
+ try {
+ const userNameFree = JSON.parse(body).data.length === 0
+ return res.status(userNameFree ? 200 : 400).send(userNameFree)
+ } catch (e) {
+ return res.status(500).send(e)
+ }
}
- })
+ )
}
diff --git a/src/backend/routes/views/account/get/confirmPasswordReset.js b/src/backend/routes/views/account/get/confirmPasswordReset.js
index 5b7d9017..73bae918 100644
--- a/src/backend/routes/views/account/get/confirmPasswordReset.js
+++ b/src/backend/routes/views/account/get/confirmPasswordReset.js
@@ -14,40 +14,48 @@ exports = module.exports = function (req, res) {
section: 'account',
formData: req.body || {},
username: req.query.username,
- token: req.query.token
+ token: req.query.token,
})
}
const renderRequestPasswordReset = async (req, res, errors) => {
- axios.post(appConfig.apiUrl + '/users/buildSteamPasswordResetUrl', {}, { maxRedirects: 0 }).then(response => {
- if (response.status !== 200) {
- throw new Error('java-api error')
- }
+ axios
+ .post(
+ appConfig.apiUrl + '/users/buildSteamPasswordResetUrl',
+ {},
+ { maxRedirects: 0 }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error('java-api error')
+ }
- errors.errors[errors.errors.length - 1].msg += '. You may request a new link here'
+ errors.errors[errors.errors.length - 1].msg +=
+ '. You may request a new link here'
- return res.render('account/requestPasswordReset', {
- section: 'account',
- errors: {
- class: 'alert-danger',
- messages: errors,
- type: 'Error!'
- },
- steamReset: response.data.steamUrl,
- formData: {},
- recaptchaSiteKey: appConfig.recaptchaKey
+ return res.render('account/requestPasswordReset', {
+ section: 'account',
+ errors: {
+ class: 'alert-danger',
+ messages: errors,
+ type: 'Error!',
+ },
+ steamReset: response.data.steamUrl,
+ formData: {},
+ recaptchaSiteKey: appConfig.recaptchaKey,
+ })
})
- }).catch(error => {
- console.error(error.toString())
- return res.render('account/requestPasswordReset', {
- section: 'account',
- errors: {
- class: 'alert-danger',
- messages: error.toString(),
- type: 'Error!'
- },
- formData: {},
- recaptchaSiteKey: appConfig.recaptchaKey
+ .catch((error) => {
+ console.error(error.toString())
+ return res.render('account/requestPasswordReset', {
+ section: 'account',
+ errors: {
+ class: 'alert-danger',
+ messages: error.toString(),
+ type: 'Error!',
+ },
+ formData: {},
+ recaptchaSiteKey: appConfig.recaptchaKey,
+ })
})
- })
}
diff --git a/src/backend/routes/views/account/get/connectSteam.js b/src/backend/routes/views/account/get/connectSteam.js
index 9dd1964a..5bc96b8c 100644
--- a/src/backend/routes/views/account/get/connectSteam.js
+++ b/src/backend/routes/views/account/get/connectSteam.js
@@ -7,44 +7,66 @@ exports = module.exports = function (req, res) {
locals.section = 'account'
const overallRes = res
- request.post({
- url: process.env.API_URL + '/users/buildSteamLinkUrl',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: { callbackUrl: req.protocol + '://' + req.get('host') + '/account/link?done' }
- }, function (err, res, body) {
- if (err) {
- flash.class = 'alert-danger'
- flash.messages = [{ msg: 'Your steam account was not successfully linked! Please verify you logged into the website correctly.' }]
- flash.type = 'Error!'
-
- return overallRes.render('account/linkSteam', { flash })
- }
- // Must not be valid, check to see if errors, otherwise return generic error.
- try {
- body = JSON.parse(body)
+ request.post(
+ {
+ url: process.env.API_URL + '/users/buildSteamLinkUrl',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ form: {
+ callbackUrl:
+ req.protocol +
+ '://' +
+ req.get('host') +
+ '/account/link?done',
+ },
+ },
+ function (err, res, body) {
+ if (err) {
+ flash.class = 'alert-danger'
+ flash.messages = [
+ {
+ msg: 'Your steam account was not successfully linked! Please verify you logged into the website correctly.',
+ },
+ ]
+ flash.type = 'Error!'
- if (body.steamUrl) {
- return overallRes.redirect(body.steamUrl)
+ return overallRes.render('account/linkSteam', { flash })
}
+ // Must not be valid, check to see if errors, otherwise return generic error.
+ try {
+ body = JSON.parse(body)
- const errorMessages = []
+ if (body.steamUrl) {
+ return overallRes.redirect(body.steamUrl)
+ }
- for (let i = 0; i < body.errors.length; i++) {
- const error = body.errors[i]
- errorMessages.push({ msg: error.detail })
- }
+ const errorMessages = []
- flash.class = 'alert-danger'
- flash.messages = errorMessages
- flash.type = 'Error!'
+ for (let i = 0; i < body.errors.length; i++) {
+ const error = body.errors[i]
+ errorMessages.push({ msg: error.detail })
+ }
- overallRes.render('account/linkSteam', { flash })
- } catch (e) {
- flash.class = 'alert-danger'
- flash.messages = [{ msg: 'Your steam account was not successfully linked! Please verify you logged into the website correctly.' }]
- flash.type = 'Error!'
+ flash.class = 'alert-danger'
+ flash.messages = errorMessages
+ flash.type = 'Error!'
- overallRes.render('account/linkSteam', { flash })
+ overallRes.render('account/linkSteam', { flash })
+ } catch (e) {
+ flash.class = 'alert-danger'
+ flash.messages = [
+ {
+ msg: 'Your steam account was not successfully linked! Please verify you logged into the website correctly.',
+ },
+ ]
+ flash.type = 'Error!'
+
+ overallRes.render('account/linkSteam', { flash })
+ }
}
- })
+ )
}
diff --git a/src/backend/routes/views/account/get/createAccount.js b/src/backend/routes/views/account/get/createAccount.js
index 0e35c4b3..c47d31b6 100644
--- a/src/backend/routes/views/account/get/createAccount.js
+++ b/src/backend/routes/views/account/get/createAccount.js
@@ -6,7 +6,8 @@ exports = module.exports = function (req, res) {
locals.section = 'account'
if (req.query.token) {
- locals.tokenURL = process.env.API_URL + '/users/activate?token=' + req.query.token
+ locals.tokenURL =
+ process.env.API_URL + '/users/activate?token=' + req.query.token
} else {
const flash = {}
flash.type = 'Error!'
diff --git a/src/backend/routes/views/account/get/linkGog.js b/src/backend/routes/views/account/get/linkGog.js
index 67191ba5..eab1422e 100644
--- a/src/backend/routes/views/account/get/linkGog.js
+++ b/src/backend/routes/views/account/get/linkGog.js
@@ -16,7 +16,7 @@ exports = module.exports = function (req, res) {
const errors = JSON.parse(req.query.errors)
flash.class = 'alert-danger'
- flash.messages = errors.map(error => ({ msg: error.detail }))
+ flash.messages = errors.map((error) => ({ msg: error.detail }))
flash.type = 'Error'
}
} else {
@@ -25,21 +25,29 @@ exports = module.exports = function (req, res) {
const overallRes = res
- request.get({
- url: process.env.API_URL + '/users/buildGogProfileToken',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: {}
- }, function (err, res, body) {
- locals.gogToken = 'unable to obtain token'
- if (err || res.statusCode !== 200) {
- flash = {}
- error.parseApiErrors(body, flash)
- return overallRes.render('account/linkGog', { flash })
+ request.get(
+ {
+ url: process.env.API_URL + '/users/buildGogProfileToken',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ form: {},
+ },
+ function (err, res, body) {
+ locals.gogToken = 'unable to obtain token'
+ if (err || res.statusCode !== 200) {
+ flash = {}
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/linkGog', { flash })
+ }
+
+ locals.gogToken = JSON.parse(body).gogToken
+
+ // Render the view
+ overallRes.render('account/linkGog', { flash })
}
-
- locals.gogToken = JSON.parse(body).gogToken
-
- // Render the view
- overallRes.render('account/linkGog', { flash })
- })
+ )
}
diff --git a/src/backend/routes/views/account/get/linkSteam.js b/src/backend/routes/views/account/get/linkSteam.js
index e29f8458..1b1c2e3b 100644
--- a/src/backend/routes/views/account/get/linkSteam.js
+++ b/src/backend/routes/views/account/get/linkSteam.js
@@ -13,11 +13,13 @@ exports = module.exports = function (req, res) {
const errors = JSON.parse(req.query.errors)
flash.class = 'alert-danger'
- flash.messages = errors.map(error => ({ msg: error.detail }))
+ flash.messages = errors.map((error) => ({ msg: error.detail }))
flash.type = 'Error'
} else {
flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your steam account has successfully been linked.' }]
+ flash.messages = [
+ { msg: 'Your steam account has successfully been linked.' },
+ ]
flash.type = 'Success'
}
} else {
@@ -25,7 +27,8 @@ exports = module.exports = function (req, res) {
}
// locals.steam = process.env.API_URL + '/users/linkToSteam';
- locals.steamConnect = req.protocol + '://' + req.get('host') + '/account/connect'
+ locals.steamConnect =
+ req.protocol + '://' + req.get('host') + '/account/connect'
// Render the view
res.render('account/linkSteam', { flash })
diff --git a/src/backend/routes/views/account/get/register.js b/src/backend/routes/views/account/get/register.js
index a955540b..072677ab 100644
--- a/src/backend/routes/views/account/get/register.js
+++ b/src/backend/routes/views/account/get/register.js
@@ -10,5 +10,8 @@ exports = module.exports = function (req, res) {
const flash = null
// Render the view
- res.render('account/register', { flash, recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY })
+ res.render('account/register', {
+ flash,
+ recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY,
+ })
}
diff --git a/src/backend/routes/views/account/get/report.js b/src/backend/routes/views/account/get/report.js
index 7dde48c0..ce033e43 100644
--- a/src/backend/routes/views/account/get/report.js
+++ b/src/backend/routes/views/account/get/report.js
@@ -1,7 +1,9 @@
const getReports = async (javaApiClient) => {
const maxDescriptionLength = 48
- const response = await javaApiClient.get('/data/moderationReport?include=reportedUsers,lastModerator&sort=-createTime')
+ const response = await javaApiClient.get(
+ '/data/moderationReport?include=reportedUsers,lastModerator&sort=-createTime'
+ )
if (response.status !== 200) {
return []
@@ -27,7 +29,10 @@ const getReports = async (javaApiClient) => {
if (report.relationships.lastModerator.data) {
for (const l in reports.included) {
const user = reports.included[l]
- if (user.type === 'player' && user.id === report.relationships.lastModerator.data.id) {
+ if (
+ user.type === 'player' &&
+ user.id === report.relationships.lastModerator.data.id
+ ) {
moderator = user.attributes.login
break
}
@@ -35,6 +40,7 @@ const getReports = async (javaApiClient) => {
}
let statusStyle = {}
+ // prettier-ignore
switch (report.attributes.reportStatus) {
case 'AWAITING':
statusStyle = { color: '#806A15', 'background-color': '#FAD147' }
@@ -55,12 +61,23 @@ const getReports = async (javaApiClient) => {
id: report.id,
offenders: offenders.join(' '),
creationTime: report.attributes.createTime,
- game: report.relationships.game.data != null ? '#' + report.relationships.game.data.id : '',
+ game:
+ report.relationships.game.data != null
+ ? '#' + report.relationships.game.data.id
+ : '',
lastModerator: moderator,
- description: report.attributes.reportDescription.substr(0, maxDescriptionLength) + (report.attributes.reportDescription.length > maxDescriptionLength ? '...' : ''),
+ description:
+ report.attributes.reportDescription.substr(
+ 0,
+ maxDescriptionLength
+ ) +
+ (report.attributes.reportDescription.length >
+ maxDescriptionLength
+ ? '...'
+ : ''),
notice: report.attributes.moderatorNotice,
status: report.attributes.reportStatus,
- statusStyle
+ statusStyle,
})
}
@@ -78,8 +95,12 @@ module.exports = async (req, res) => {
if (req.originalUrl === '/report_submitted') {
flash = {
class: 'alert-success ',
- messages: { errors: [{ msg: 'You have successfully submitted your report' }] },
- type: 'Success!'
+ messages: {
+ errors: [
+ { msg: 'You have successfully submitted your report' },
+ ],
+ },
+ type: 'Success!',
}
} else if (req.query.flash) {
const buff = Buffer.from(req.query.flash, 'base64')
@@ -94,6 +115,6 @@ module.exports = async (req, res) => {
flash,
reports: await getReports(req.requestContainer.get('JavaApiClient')),
reportable_members: {},
- offenders_names: offendersNames
+ offenders_names: offendersNames,
})
}
diff --git a/src/backend/routes/views/account/get/requestPasswordReset.js b/src/backend/routes/views/account/get/requestPasswordReset.js
index 4505ed01..b4d4325c 100644
--- a/src/backend/routes/views/account/get/requestPasswordReset.js
+++ b/src/backend/routes/views/account/get/requestPasswordReset.js
@@ -5,30 +5,37 @@ exports = module.exports = async function (req, res) {
const formData = req.body || {}
// funky issue: https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express
- await new Promise(resolve => process.nextTick(resolve))
+ await new Promise((resolve) => process.nextTick(resolve))
- axios.post(appConfig.apiUrl + '/users/buildSteamPasswordResetUrl', {}, { maxRedirects: 0 }).then(response => {
- if (response.status !== 200) {
- throw new Error('java-api error')
- }
+ axios
+ .post(
+ appConfig.apiUrl + '/users/buildSteamPasswordResetUrl',
+ {},
+ { maxRedirects: 0 }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error('java-api error')
+ }
- res.render('account/requestPasswordReset', {
- section: 'account',
- steamReset: response.data.steamUrl,
- formData,
- recaptchaSiteKey: appConfig.recaptchaKey
+ res.render('account/requestPasswordReset', {
+ section: 'account',
+ steamReset: response.data.steamUrl,
+ formData,
+ recaptchaSiteKey: appConfig.recaptchaKey,
+ })
})
- }).catch(error => {
- console.error(error.toString())
- res.render('account/requestPasswordReset', {
- section: 'account',
- errors: {
- class: 'alert-danger',
- messages: error.toString,
- type: 'Error!'
- },
- formData,
- recaptchaSiteKey: appConfig.recaptchaKey
+ .catch((error) => {
+ console.error(error.toString())
+ res.render('account/requestPasswordReset', {
+ section: 'account',
+ errors: {
+ class: 'alert-danger',
+ messages: error.toString,
+ type: 'Error!',
+ },
+ formData,
+ recaptchaSiteKey: appConfig.recaptchaKey,
+ })
})
- })
}
diff --git a/src/backend/routes/views/account/get/resync.js b/src/backend/routes/views/account/get/resync.js
index 2cbd71be..64bb9eea 100644
--- a/src/backend/routes/views/account/get/resync.js
+++ b/src/backend/routes/views/account/get/resync.js
@@ -13,20 +13,29 @@ exports = module.exports = function (req, res) {
const overallRes = res
- request.post({
- url: process.env.API_URL + '/users/resyncAccount',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- } else {
- // Successfully account resync
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your account was resynced successfully.' }]
- flash.type = 'Success!'
- }
+ request.post(
+ {
+ url: process.env.API_URL + '/users/resyncAccount',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ } else {
+ // Successfully account resync
+ flash.class = 'alert-success'
+ flash.messages = [
+ { msg: 'Your account was resynced successfully.' },
+ ]
+ flash.type = 'Success!'
+ }
- overallRes.render('account/confirmResyncAccount', { flash })
- }
+ overallRes.render('account/confirmResyncAccount', { flash })
+ }
)
}
diff --git a/src/backend/routes/views/account/post/activate.js b/src/backend/routes/views/account/post/activate.js
index 7e311276..000f5d02 100644
--- a/src/backend/routes/views/account/post/activate.js
+++ b/src/backend/routes/views/account/post/activate.js
@@ -12,8 +12,10 @@ exports = module.exports = function (req, res) {
// validate the input
check('password', 'Password is required').notEmpty()
- check('password', 'Password must be six or more characters').isLength({ min: 6 })
- check('password', 'Passwords don\'t match').equals(req.body.password_confirm)
+ check('password', 'Password must be six or more characters').isLength({
+ min: 6,
+ })
+ check('password', "Passwords don't match").equals(req.body.password_confirm)
// check the validation object for errors
const errors = validationResult(req)
@@ -32,22 +34,26 @@ exports = module.exports = function (req, res) {
const overallRes = res
// Run post to reset endpoint
- request.post({
- url: process.env.API_URL + '/users/activate',
- form: { password, token }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/activate', { flash })
+ request.post(
+ {
+ url: process.env.API_URL + '/users/activate',
+ form: { password, token },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/activate', { flash })
+ }
+
+ // Successfully reset password
+ flash.class = 'alert-success'
+ flash.messages = [
+ { msg: 'Your account was created successfully.' },
+ ]
+ flash.type = 'Success!'
+
+ overallRes.render('account/activate', { flash })
}
-
- // Successfully reset password
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your account was created successfully.' }]
- flash.type = 'Success!'
-
- overallRes.render('account/activate', { flash })
- }
)
}
}
diff --git a/src/backend/routes/views/account/post/changeEmail.js b/src/backend/routes/views/account/post/changeEmail.js
index 0c006e93..e26c370a 100644
--- a/src/backend/routes/views/account/post/changeEmail.js
+++ b/src/backend/routes/views/account/post/changeEmail.js
@@ -17,36 +17,44 @@ exports = module.exports = function (req, res) {
// Must have client side errors to fix
if (!errors.isEmpty()) {
- // failure
+ // failure
flash.class = 'alert-danger'
flash.messages = errors
flash.type = 'Error!'
res.render('account/changeEmail', { flash })
} else {
- // pull the form variables off the request body
+ // pull the form variables off the request body
const email = req.body.email
const password = req.body.password
const overallRes = res
- request.post({
- url: `${process.env.API_URL}/users/changeEmail`,
- headers: { Authorization: `Bearer ${req.requestContainer.get('UserService').getUser()?.oAuthPassport.token}` },
- form: { newEmail: email, currentPassword: password }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/changeEmail', { flash })
+ request.post(
+ {
+ url: `${process.env.API_URL}/users/changeEmail`,
+ headers: {
+ Authorization: `Bearer ${
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token
+ }`,
+ },
+ form: { newEmail: email, currentPassword: password },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/changeEmail', { flash })
+ }
+
+ // Successfully changed email
+ flash.class = 'alert-success'
+ flash.messages = [{ msg: 'Your email was set successfully.' }]
+ flash.type = 'Success!'
+
+ overallRes.render('account/changeEmail', { flash })
}
-
- // Successfully changed email
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your email was set successfully.' }]
- flash.type = 'Success!'
-
- overallRes.render('account/changeEmail', { flash })
- })
+ )
}
}
diff --git a/src/backend/routes/views/account/post/changePassword.js b/src/backend/routes/views/account/post/changePassword.js
index b1e790f7..1951009e 100644
--- a/src/backend/routes/views/account/post/changePassword.js
+++ b/src/backend/routes/views/account/post/changePassword.js
@@ -10,19 +10,28 @@ exports = module.exports = function (req, res) {
// validate the input
check('old_password', 'Old Password is required').notEmpty()
- check('old_password', 'Old Password must be six or more characters').isLength({ min: 6 })
+ check(
+ 'old_password',
+ 'Old Password must be six or more characters'
+ ).isLength({ min: 6 })
check('password', 'New Password is required').notEmpty()
- check('password', 'New Password must be six or more characters').isLength({ min: 6 })
- check('password', 'New Passwords don\'t match').equals(req.body.password_confirm)
+ check('password', 'New Password must be six or more characters').isLength({
+ min: 6,
+ })
+ check('password', "New Passwords don't match").equals(
+ req.body.password_confirm
+ )
check('username', 'Username is required').notEmpty()
- check('username', 'Username must be three or more characters').isLength({ min: 3 })
+ check('username', 'Username must be three or more characters').isLength({
+ min: 3,
+ })
// check the validation object for errors
const errors = validationResult(req)
// Must have client side errors to fix
if (!errors.isEmpty()) {
- // failure
+ // failure
flash.class = 'alert-danger'
flash.messages = errors
flash.type = 'Error!'
@@ -35,22 +44,36 @@ exports = module.exports = function (req, res) {
const overallRes = res
// Run post to reset endpoint
- request.post({
- url: process.env.API_URL + '/users/changePassword',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: { currentPassword: oldPassword, newPassword }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/changePassword', { flash })
- }
+ request.post(
+ {
+ url: process.env.API_URL + '/users/changePassword',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ form: { currentPassword: oldPassword, newPassword },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/changePassword', {
+ flash,
+ })
+ }
- // Successfully reset password
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your password was changed successfully. Please use the new password to log in!' }]
- flash.type = 'Success!'
+ // Successfully reset password
+ flash.class = 'alert-success'
+ flash.messages = [
+ {
+ msg: 'Your password was changed successfully. Please use the new password to log in!',
+ },
+ ]
+ flash.type = 'Success!'
- overallRes.render('account/changePassword', { flash })
- })
+ overallRes.render('account/changePassword', { flash })
+ }
+ )
}
}
diff --git a/src/backend/routes/views/account/post/changeUsername.js b/src/backend/routes/views/account/post/changeUsername.js
index 0de42722..d92d3c82 100644
--- a/src/backend/routes/views/account/post/changeUsername.js
+++ b/src/backend/routes/views/account/post/changeUsername.js
@@ -10,7 +10,9 @@ exports = module.exports = function (req, res) {
// validate the input
check('username', 'Username is required').notEmpty()
- check('username', 'Username must be three or more characters').isLength({ min: 3 })
+ check('username', 'Username must be three or more characters').isLength({
+ min: 3,
+ })
// check the validation object for errors
const errors = validationResult(req)
@@ -24,27 +26,41 @@ exports = module.exports = function (req, res) {
res.render('account/changeUsername', { flash })
} else {
- // pull the form variables off the request body
+ // pull the form variables off the request body
const username = req.body.username
const overallRes = res
// Run post to reset endpoint
- request.post({
- url: process.env.API_URL + '/users/changeUsername',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: { newUsername: username }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/changeUsername', { flash })
- }
+ request.post(
+ {
+ url: process.env.API_URL + '/users/changeUsername',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ form: { newUsername: username },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/changeUsername', {
+ flash,
+ })
+ }
- // Successfully changed username
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your username was changed successfully. Please use the new username to log in!' }]
- flash.type = 'Success!'
+ // Successfully changed username
+ flash.class = 'alert-success'
+ flash.messages = [
+ {
+ msg: 'Your username was changed successfully. Please use the new username to log in!',
+ },
+ ]
+ flash.type = 'Success!'
- overallRes.render('account/changeUsername', { flash })
- })
+ overallRes.render('account/changeUsername', { flash })
+ }
+ )
}
}
diff --git a/src/backend/routes/views/account/post/confirmPasswordReset.js b/src/backend/routes/views/account/post/confirmPasswordReset.js
index 52e42c94..11579412 100644
--- a/src/backend/routes/views/account/post/confirmPasswordReset.js
+++ b/src/backend/routes/views/account/post/confirmPasswordReset.js
@@ -12,8 +12,10 @@ exports = module.exports = function (req, res) {
// validate the input
check('password', 'Password is required').notEmpty()
- check('password', 'Password must be six or more characters').isLength({ min: 6 })
- check('password', 'Passwords don\'t match').equals(req.body.password_confirm)
+ check('password', 'Password must be six or more characters').isLength({
+ min: 6,
+ })
+ check('password', "Passwords don't match").equals(req.body.password_confirm)
// check the validation object for errors
const errors = validationResult(req)
@@ -32,22 +34,28 @@ exports = module.exports = function (req, res) {
const overallRes = res
// Run post to reset endpoint
- request.post({
- url: process.env.API_URL + '/users/performPasswordReset',
- form: { newPassword, token }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/confirmPasswordReset', { flash })
+ request.post(
+ {
+ url: process.env.API_URL + '/users/performPasswordReset',
+ form: { newPassword, token },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/confirmPasswordReset', {
+ flash,
+ })
+ }
+
+ // Successfully reset password
+ flash.class = 'alert-success'
+ flash.messages = [
+ { msg: 'Your password was changed successfully.' },
+ ]
+ flash.type = 'Success!'
+
+ overallRes.render('account/confirmPasswordReset', { flash })
}
-
- // Successfully reset password
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your password was changed successfully.' }]
- flash.type = 'Success!'
-
- overallRes.render('account/confirmPasswordReset', { flash })
- }
)
}
}
diff --git a/src/backend/routes/views/account/post/error.js b/src/backend/routes/views/account/post/error.js
index 175092da..d15c6f66 100644
--- a/src/backend/routes/views/account/post/error.js
+++ b/src/backend/routes/views/account/post/error.js
@@ -4,18 +4,24 @@ module.exports = {
try {
const response = JSON.parse(body)
- response.errors.forEach(error => errorMessages.push({ msg: error.detail }))
+ response.errors.forEach((error) =>
+ errorMessages.push({ msg: error.detail })
+ )
} catch (e) {
- errorMessages.push({ msg: 'An unknown error occurred. Please try again later or ask the support.' })
+ errorMessages.push({
+ msg: 'An unknown error occurred. Please try again later or ask the support.',
+ })
console.log('Error on parsing server response: ' + body)
}
if (errorMessages.length === 0) {
- errorMessages.push({ msg: 'An unknown error occurred. Please try again later or ask the support.' })
+ errorMessages.push({
+ msg: 'An unknown error occurred. Please try again later or ask the support.',
+ })
}
flash.class = 'alert-danger'
flash.messages = errorMessages
flash.type = 'Error!'
- }
+ },
}
diff --git a/src/backend/routes/views/account/post/linkGog.js b/src/backend/routes/views/account/post/linkGog.js
index 17a341e6..93861c31 100644
--- a/src/backend/routes/views/account/post/linkGog.js
+++ b/src/backend/routes/views/account/post/linkGog.js
@@ -10,15 +10,19 @@ exports = module.exports = function (req, res) {
// validate the input
check('gog_username', 'Username is required').notEmpty()
- check('gog_username', 'Username must be at least 3 characters').isLength({ min: 3 })
- check('gog_username', 'Username must be at most 100 characters').isLength({ max: 100 })
+ check('gog_username', 'Username must be at least 3 characters').isLength({
+ min: 3,
+ })
+ check('gog_username', 'Username must be at most 100 characters').isLength({
+ max: 100,
+ })
// check the validation object for errors
const errors = validationResult(req)
// Must have client side errors to fix
if (!errors.isEmpty()) {
- // failure
+ // failure
flash.class = 'alert-danger'
flash.messages = errors
flash.type = 'Error!'
@@ -29,41 +33,66 @@ exports = module.exports = function (req, res) {
const overallRes = res
- request.post({
- url: process.env.API_URL + '/users/linkToGog',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: { gogUsername }
- }, function (err, res, body) {
- if (!err && res.statusCode === 200) {
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your accounts were linked successfully.' }]
- flash.type = 'Success!'
+ request.post(
+ {
+ url: process.env.API_URL + '/users/linkToGog',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer.get('UserService').getUser()
+ ?.oAuthPassport.token,
+ },
+ form: { gogUsername },
+ },
+ function (err, res, body) {
+ if (!err && res.statusCode === 200) {
+ flash.class = 'alert-success'
+ flash.messages = [
+ { msg: 'Your accounts were linked successfully.' },
+ ]
+ flash.type = 'Success!'
- locals.gogToken = '-'
- overallRes.render('account/linkGog', { flash })
- } else {
- error.parseApiErrors(body, flash)
+ locals.gogToken = '-'
+ overallRes.render('account/linkGog', { flash })
+ } else {
+ error.parseApiErrors(body, flash)
- // We need the gog token on the error page as well,
- // this code literally does the same as linkGog.js, but due to the architectural structure of this application
- // it's not possible to extract it into a separate function while saving any code
- request.get({
- url: process.env.API_URL + '/users/buildGogProfileToken',
- headers: { Authorization: 'Bearer ' + req.requestContainer.get('UserService').getUser()?.oAuthPassport.token },
- form: {}
- }, function (err, res, body) {
- locals.gogToken = 'unable to obtain token'
- if (err || res.statusCode !== 200) {
- flash = {}
- error.parseApiErrors(body, flash)
- return overallRes.render('account/linkGog', { flash })
- }
+ // We need the gog token on the error page as well,
+ // this code literally does the same as linkGog.js, but due to the architectural structure of this application
+ // it's not possible to extract it into a separate function while saving any code
+ request.get(
+ {
+ url:
+ process.env.API_URL +
+ '/users/buildGogProfileToken',
+ headers: {
+ Authorization:
+ 'Bearer ' +
+ req.requestContainer
+ .get('UserService')
+ .getUser()?.oAuthPassport.token,
+ },
+ form: {},
+ },
+ function (err, res, body) {
+ locals.gogToken = 'unable to obtain token'
+ if (err || res.statusCode !== 200) {
+ flash = {}
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/linkGog', {
+ flash,
+ })
+ }
- locals.gogToken = JSON.parse(body).gogToken
+ locals.gogToken = JSON.parse(body).gogToken
- return overallRes.render('account/linkGog', { flash })
- })
+ return overallRes.render('account/linkGog', {
+ flash,
+ })
+ }
+ )
+ }
}
- })
+ )
}
}
diff --git a/src/backend/routes/views/account/post/register.js b/src/backend/routes/views/account/post/register.js
index 54f337b8..3d293b93 100644
--- a/src/backend/routes/views/account/post/register.js
+++ b/src/backend/routes/views/account/post/register.js
@@ -9,7 +9,9 @@ exports = module.exports = function (req, res) {
locals.formData = req.body || {}
// validate the input
check('username', 'Username is required').notEmpty()
- check('username', 'Username must be three or more characters').isLength({ min: 3 })
+ check('username', 'Username must be three or more characters').isLength({
+ min: 3,
+ })
check('email', 'Email is required').notEmpty()
check('email', 'Email does not appear to be valid').isEmail()
@@ -18,14 +20,14 @@ exports = module.exports = function (req, res) {
// Must have client side errors to fix
if (!errors.isEmpty()) {
- // failure
+ // failure
flash.class = 'alert-danger'
flash.messages = errors
flash.type = 'Error!'
res.render('account/register', { flash })
} else {
- // pull the form variables off the request body
+ // pull the form variables off the request body
const username = req.body.username
const email = req.body.email
const recaptchaResponse = req.body['g-recaptcha-response']
@@ -33,45 +35,60 @@ exports = module.exports = function (req, res) {
const overallRes = res
// Run post to register endpoint
- request.post({
- url: process.env.API_URL + '/users/register',
- form: { username, email, recaptchaResponse }
- }, function (err, res, body) {
- let resp
- const errorMessages = []
+ request.post(
+ {
+ url: process.env.API_URL + '/users/register',
+ form: { username, email, recaptchaResponse },
+ },
+ function (err, res, body) {
+ let resp
+ const errorMessages = []
+
+ if (err || res.statusCode !== 200) {
+ try {
+ resp = JSON.parse(body)
+ } catch (e) {
+ errorMessages.push({
+ msg: 'Invalid registration sign up. Please try again later.',
+ })
+ flash.class = 'alert-danger'
+ flash.messages = errorMessages
+ flash.type = 'Error!'
+
+ return overallRes.render('account/register', {
+ flash,
+ recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY,
+ })
+ }
+
+ // Failed registering user
+ for (let i = 0; i < resp.errors.length; i++) {
+ const error = resp.errors[i]
+
+ errorMessages.push({ msg: error.detail })
+ }
- if (err || res.statusCode !== 200) {
- try {
- resp = JSON.parse(body)
- } catch (e) {
- errorMessages.push({ msg: 'Invalid registration sign up. Please try again later.' })
flash.class = 'alert-danger'
flash.messages = errorMessages
flash.type = 'Error!'
- return overallRes.render('account/register', { flash, recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY })
+ return overallRes.render('account/register', {
+ flash,
+ recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY,
+ })
}
- // Failed registering user
- for (let i = 0; i < resp.errors.length; i++) {
- const error = resp.errors[i]
+ // Successfully registered user
+ flash.class = 'alert-success'
+ flash.messages = [
+ {
+ msg: 'Please check your email to verify your registration. Then you will be ready to log in!',
+ },
+ ]
+ flash.type = 'Success!'
- errorMessages.push({ msg: error.detail })
- }
-
- flash.class = 'alert-danger'
- flash.messages = errorMessages
- flash.type = 'Error!'
-
- return overallRes.render('account/register', { flash, recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY })
+ overallRes.render('account/register', { flash })
}
-
- // Successfully registered user
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Please check your email to verify your registration. Then you will be ready to log in!' }]
- flash.type = 'Success!'
-
- overallRes.render('account/register', { flash })
- })
+ )
}
}
diff --git a/src/backend/routes/views/account/post/report.js b/src/backend/routes/views/account/post/report.js
index 0eb05d3b..5a06616d 100644
--- a/src/backend/routes/views/account/post/report.js
+++ b/src/backend/routes/views/account/post/report.js
@@ -2,19 +2,34 @@ const { validationResult, body, matchedData } = require('express-validator')
const getReportRoute = require('../get/report')
exports = module.exports = [
- body('offender', 'Offender invalid').isString().isLength({ min: 2, max: 50 }).trim().escape(),
- body('report_description', 'Please describe the incident').notEmpty().isLength({ min: 2, max: 2000 }).trim().escape(),
- body('game_id', 'Please enter a valid game ID, or nothing. The # is not needed.').optional({ values: 'falsy' }).isDecimal(),
+ body('offender', 'Offender invalid')
+ .isString()
+ .isLength({ min: 2, max: 50 })
+ .trim()
+ .escape(),
+ body('report_description', 'Please describe the incident')
+ .notEmpty()
+ .isLength({ min: 2, max: 2000 })
+ .trim()
+ .escape(),
+ body(
+ 'game_id',
+ 'Please enter a valid game ID, or nothing. The # is not needed.'
+ )
+ .optional({ values: 'falsy' })
+ .isDecimal(),
async function (req, res) {
const javaApiClient = req.requestContainer.get('JavaApiClient')
const errors = validationResult(req)
if (!errors.isEmpty()) {
- req.query.flash = Buffer.from(JSON.stringify({
- class: 'alert-danger',
- messages: errors,
- type: 'Error!'
- })).toString('base64')
+ req.query.flash = Buffer.from(
+ JSON.stringify({
+ class: 'alert-danger',
+ messages: errors,
+ type: 'Error!',
+ })
+ ).toString('base64')
return getReportRoute(req, res)
}
@@ -23,7 +38,11 @@ exports = module.exports = [
let apiUsers
try {
- const userFetch = await javaApiClient.get('/data/player?filter=login==' + encodeURIComponent(formData.offender) + '&fields[player]=login&page[size]=1')
+ const userFetch = await javaApiClient.get(
+ '/data/player?filter=login==' +
+ encodeURIComponent(formData.offender) +
+ '&fields[player]=login&page[size]=1'
+ )
if (userFetch.status !== 200) {
throw new Error('issues getting players')
@@ -31,11 +50,15 @@ exports = module.exports = [
apiUsers = JSON.parse(userFetch.data)
} catch (e) {
- req.query.flash = Buffer.from(JSON.stringify({
- class: 'alert-danger',
- messages: { errors: [{ msg: 'Error while fetching offender' }] },
- type: 'Error!'
- })).toString('base64')
+ req.query.flash = Buffer.from(
+ JSON.stringify({
+ class: 'alert-danger',
+ messages: {
+ errors: [{ msg: 'Error while fetching offender' }],
+ },
+ type: 'Error!',
+ })
+ ).toString('base64')
return getReportRoute(req, res)
}
@@ -43,17 +66,30 @@ exports = module.exports = [
// Mapping users to their IDs
let offenderId = null
apiUsers.data.forEach((user) => {
- if (user.attributes.login.toUpperCase() === formData.offender.toUpperCase()) {
+ if (
+ user.attributes.login.toUpperCase() ===
+ formData.offender.toUpperCase()
+ ) {
offenderId = user.id
}
})
if (!offenderId) {
- req.query.flash = Buffer.from(JSON.stringify({
- class: 'alert-danger',
- messages: { errors: [{ msg: 'The following user could not be found : ' + formData.offender }] },
- type: 'Error!'
- })).toString('base64')
+ req.query.flash = Buffer.from(
+ JSON.stringify({
+ class: 'alert-danger',
+ messages: {
+ errors: [
+ {
+ msg:
+ 'The following user could not be found : ' +
+ formData.offender,
+ },
+ ],
+ },
+ type: 'Error!',
+ })
+ ).toString('base64')
return getReportRoute(req, res)
}
@@ -61,16 +97,27 @@ exports = module.exports = [
// Checking the game exists
if (formData.game_id != null) {
try {
- const response = await javaApiClient.get('/data/game?filter=id==' + encodeURIComponent(formData.game_id))
+ const response = await javaApiClient.get(
+ '/data/game?filter=id==' +
+ encodeURIComponent(formData.game_id)
+ )
if (response.status !== 200) {
throw new Error('issues getting game')
}
} catch (e) {
- req.query.flash = Buffer.from(JSON.stringify({
- class: 'alert-danger',
- messages: { errors: [{ msg: 'The game could not be found. Please check the game ID you provided.' }] },
- type: 'Error!'
- })).toString('base64')
+ req.query.flash = Buffer.from(
+ JSON.stringify({
+ class: 'alert-danger',
+ messages: {
+ errors: [
+ {
+ msg: 'The game could not be found. Please check the game ID you provided.',
+ },
+ ],
+ },
+ type: 'Error!',
+ })
+ ).toString('base64')
return getReportRoute(req, res)
}
@@ -78,49 +125,69 @@ exports = module.exports = [
const relationShips = {
reportedUsers: {
- data: [{
- type: 'player',
- id: '' + offenderId
- }]
- }
+ data: [
+ {
+ type: 'player',
+ id: '' + offenderId,
+ },
+ ],
+ },
}
if (formData.game_id != null) {
- relationShips.game = { data: { type: 'game', id: '' + formData.game_id } }
+ relationShips.game = {
+ data: { type: 'game', id: '' + formData.game_id },
+ }
}
- const report =
- {
- data: [
- {
- type: 'moderationReport',
- attributes: {
- gameIncidentTimecode: (formData.game_timecode ? formData.game_timecode : null),
- reportDescription: formData.report_description
- },
- relationships: relationShips
- }
- ]
- }
+ const report = {
+ data: [
+ {
+ type: 'moderationReport',
+ attributes: {
+ gameIncidentTimecode: formData.game_timecode
+ ? formData.game_timecode
+ : null,
+ reportDescription: formData.report_description,
+ },
+ relationships: relationShips,
+ },
+ ],
+ }
- const resp = await javaApiClient.post('/data/moderationReport', JSON.stringify(report), {
- headers: {
- 'Content-Type': 'application/vnd.api+json',
- Accept: 'application/vnd.api+json'
+ const resp = await javaApiClient.post(
+ '/data/moderationReport',
+ JSON.stringify(report),
+ {
+ headers: {
+ 'Content-Type': 'application/vnd.api+json',
+ Accept: 'application/vnd.api+json',
+ },
}
- })
+ )
if (resp.status !== 201) {
const apiError = JSON.parse(resp.data)
- req.query.flash = Buffer.from(JSON.stringify({
- class: 'alert-danger',
- messages: { errors: [{ msg: 'Error while submitting the report form' }, { msg: apiError.errors?.[0]?.detail || 'unknown api error' }] },
- type: 'Error!'
- })).toString('base64')
+ req.query.flash = Buffer.from(
+ JSON.stringify({
+ class: 'alert-danger',
+ messages: {
+ errors: [
+ { msg: 'Error while submitting the report form' },
+ {
+ msg:
+ apiError.errors?.[0]?.detail ||
+ 'unknown api error',
+ },
+ ],
+ },
+ type: 'Error!',
+ })
+ ).toString('base64')
return getReportRoute(req, res)
}
res.redirect('../report_submitted')
- }
+ },
]
diff --git a/src/backend/routes/views/account/post/requestPasswordReset.js b/src/backend/routes/views/account/post/requestPasswordReset.js
index 7ce18670..8283c49e 100644
--- a/src/backend/routes/views/account/post/requestPasswordReset.js
+++ b/src/backend/routes/views/account/post/requestPasswordReset.js
@@ -28,27 +28,34 @@ exports = module.exports = function (req, res) {
const overallRes = res
// Run post to reset endpoint
- request.post({
- url: process.env.API_URL + '/users/requestPasswordReset',
- form: { identifier, recaptchaResponse }
- }, function (err, res, body) {
- if (err || res.statusCode !== 200) {
- error.parseApiErrors(body, flash)
- return overallRes.render('account/requestPasswordReset', {
+ request.post(
+ {
+ url: process.env.API_URL + '/users/requestPasswordReset',
+ form: { identifier, recaptchaResponse },
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 200) {
+ error.parseApiErrors(body, flash)
+ return overallRes.render('account/requestPasswordReset', {
+ flash,
+ recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY,
+ })
+ }
+
+ // Successfully reset password
+ flash.class = 'alert-success'
+ flash.messages = [
+ {
+ msg: 'Your password is in the process of being reset, please reset your password by clicking on the link provided in an email.',
+ },
+ ]
+ flash.type = 'Success!'
+
+ overallRes.render('account/requestPasswordReset', {
flash,
- recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY
+ recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY,
})
}
-
- // Successfully reset password
- flash.class = 'alert-success'
- flash.messages = [{ msg: 'Your password is in the process of being reset, please reset your password by clicking on the link provided in an email.' }]
- flash.type = 'Success!'
-
- overallRes.render('account/requestPasswordReset', {
- flash,
- recaptchaSiteKey: process.env.RECAPTCHA_SITE_KEY
- })
- })
+ )
}
}
diff --git a/src/backend/routes/views/accountRouter.js b/src/backend/routes/views/accountRouter.js
index 976ce7f7..63442258 100644
--- a/src/backend/routes/views/accountRouter.js
+++ b/src/backend/routes/views/accountRouter.js
@@ -5,36 +5,94 @@ const { query } = require('express-validator')
const middlewares = require('../middleware')
const url = require('url')
-router.get('/linkGog', middlewares.isAuthenticated(), require('./account/get/linkGog'))
-router.post('/linkGog', middlewares.isAuthenticated(), require('./account/post/linkGog'))
+router.get(
+ '/linkGog',
+ middlewares.isAuthenticated(),
+ require('./account/get/linkGog')
+)
+router.post(
+ '/linkGog',
+ middlewares.isAuthenticated(),
+ require('./account/post/linkGog')
+)
-router.get('/report', middlewares.isAuthenticated(), require('./account/get/report'))
-router.post('/report', middlewares.isAuthenticated(), require('./account/post/report'))
+router.get(
+ '/report',
+ middlewares.isAuthenticated(),
+ require('./account/get/report')
+)
+router.post(
+ '/report',
+ middlewares.isAuthenticated(),
+ require('./account/post/report')
+)
-router.get('/changePassword', middlewares.isAuthenticated(), require('./account/get/changePassword'))
-router.post('/changePassword', middlewares.isAuthenticated(), require('./account/post/changePassword'))
+router.get(
+ '/changePassword',
+ middlewares.isAuthenticated(),
+ require('./account/get/changePassword')
+)
+router.post(
+ '/changePassword',
+ middlewares.isAuthenticated(),
+ require('./account/post/changePassword')
+)
-router.get('/changeEmail', middlewares.isAuthenticated(), require('./account/get/changeEmail'))
-router.post('/changeEmail', middlewares.isAuthenticated(), require('./account/post/changeEmail'))
+router.get(
+ '/changeEmail',
+ middlewares.isAuthenticated(),
+ require('./account/get/changeEmail')
+)
+router.post(
+ '/changeEmail',
+ middlewares.isAuthenticated(),
+ require('./account/post/changeEmail')
+)
-router.get('/changeUsername', middlewares.isAuthenticated(), require('./account/get/changeUsername'))
-router.post('/changeUsername', middlewares.isAuthenticated(), require('./account/post/changeUsername'))
+router.get(
+ '/changeUsername',
+ middlewares.isAuthenticated(),
+ require('./account/get/changeUsername')
+)
+router.post(
+ '/changeUsername',
+ middlewares.isAuthenticated(),
+ require('./account/post/changeUsername')
+)
-router.get('/password/confirmReset', [query('token').notEmpty().withMessage('Missing token'),
- query('username').notEmpty().withMessage('Missing username')],
-require('./account/get/confirmPasswordReset'))
-router.post('/password/confirmReset', require('./account/post/confirmPasswordReset'))
+router.get(
+ '/password/confirmReset',
+ [
+ query('token').notEmpty().withMessage('Missing token'),
+ query('username').notEmpty().withMessage('Missing username'),
+ ],
+ require('./account/get/confirmPasswordReset')
+)
+router.post(
+ '/password/confirmReset',
+ require('./account/post/confirmPasswordReset')
+)
-router.get('/requestPasswordReset', require('./account/get/requestPasswordReset'))
-router.post('/requestPasswordReset', require('./account/post/requestPasswordReset'))
+router.get(
+ '/requestPasswordReset',
+ require('./account/get/requestPasswordReset')
+)
+router.post(
+ '/requestPasswordReset',
+ require('./account/post/requestPasswordReset')
+)
// still used in other applications (user-service, game-client etc.)
-router.get('/password/reset', (req, res) => res.redirect('/account/requestPasswordReset'))
+router.get('/password/reset', (req, res) =>
+ res.redirect('/account/requestPasswordReset')
+)
router.get('/confirmPasswordReset', (req, res) => {
- res.redirect(url.format({
- pathname: '/account/password/confirmReset',
- query: req.query
- }))
+ res.redirect(
+ url.format({
+ pathname: '/account/password/confirmReset',
+ query: req.query,
+ })
+ )
})
router.get('/register', require('./account/get/register'))
@@ -44,9 +102,21 @@ router.get('/activate', require('./account/get/activate'))
router.post('/activate', require('./account/post/activate'))
router.get('/checkUsername', require('./checkUsername'))
-router.get('/resync', middlewares.isAuthenticated(), require('./account/get/resync'))
+router.get(
+ '/resync',
+ middlewares.isAuthenticated(),
+ require('./account/get/resync')
+)
router.get('/create', require('./account/get/createAccount'))
-router.get('/link', middlewares.isAuthenticated(), require('./account/get/linkSteam'))
-router.get('/connect', middlewares.isAuthenticated(), require('./account/get/connectSteam'))
+router.get(
+ '/link',
+ middlewares.isAuthenticated(),
+ require('./account/get/linkSteam')
+)
+router.get(
+ '/connect',
+ middlewares.isAuthenticated(),
+ require('./account/get/connectSteam')
+)
module.exports = router
diff --git a/src/backend/routes/views/auth.js b/src/backend/routes/views/auth.js
index 75d5f00e..4693121c 100644
--- a/src/backend/routes/views/auth.js
+++ b/src/backend/routes/views/auth.js
@@ -12,7 +12,10 @@ router.get(
return next()
},
- passport.authenticate(appConfig.oauth.strategy, { failureRedirect: '/login', failureFlash: true }),
+ passport.authenticate(appConfig.oauth.strategy, {
+ failureRedirect: '/login',
+ failureFlash: true,
+ }),
(req, res) => {
res.redirect(res.locals.returnTo || '/')
}
diff --git a/src/backend/routes/views/checkUsername.js b/src/backend/routes/views/checkUsername.js
index 1d814e3c..9f11ec59 100644
--- a/src/backend/routes/views/checkUsername.js
+++ b/src/backend/routes/views/checkUsername.js
@@ -3,17 +3,20 @@ const request = require('request')
exports = module.exports = function (req, res) {
const name = req.query.username
- request(process.env.API_URL + '/data/player?filter=login==' + encodeURI(name), function (error, response, body) {
- if (error) {
- console.error(error)
- return res.status(500).send(error)
- }
+ request(
+ process.env.API_URL + '/data/player?filter=login==' + encodeURI(name),
+ function (error, response, body) {
+ if (error) {
+ console.error(error)
+ return res.status(500).send(error)
+ }
- try {
- const userNameFree = JSON.parse(body).data.length === 0
- return res.status(userNameFree ? 200 : 400).send(userNameFree)
- } catch (e) {
- return res.status(500).send(e)
+ try {
+ const userNameFree = JSON.parse(body).data.length === 0
+ return res.status(userNameFree ? 200 : 400).send(userNameFree)
+ } catch (e) {
+ return res.status(500).send(e)
+ }
}
- })
+ )
}
diff --git a/src/backend/routes/views/clanRouter.js b/src/backend/routes/views/clanRouter.js
index 30f16756..18aeac1e 100644
--- a/src/backend/routes/views/clanRouter.js
+++ b/src/backend/routes/views/clanRouter.js
@@ -11,16 +11,36 @@ router.get('/view/:id', require('./clans/view'))
router.get('/create', middlewares.isAuthenticated(), create)
router.post('/create', middlewares.isAuthenticated(), create)
-router.get('/manage', middlewares.isAuthenticated(), require('./clans/get/manage'))
-router.post('/update', middlewares.isAuthenticated(), require('./clans/post/update'))
-router.post('/destroy', middlewares.isAuthenticated(), require('./clans/post/destroy'))
+router.get(
+ '/manage',
+ middlewares.isAuthenticated(),
+ require('./clans/get/manage')
+)
+router.post(
+ '/update',
+ middlewares.isAuthenticated(),
+ require('./clans/post/update')
+)
+router.post(
+ '/destroy',
+ middlewares.isAuthenticated(),
+ require('./clans/post/destroy')
+)
router.get('/invite', middlewares.isAuthenticated(), invite)
router.post('/invite', middlewares.isAuthenticated(), invite)
-router.get('/kick/:memberId', middlewares.isAuthenticated(), require('./clans/kick'))
+router.get(
+ '/kick/:memberId',
+ middlewares.isAuthenticated(),
+ require('./clans/kick')
+)
router.get('/leave', middlewares.isAuthenticated(), leave)
router.post('/leave', middlewares.isAuthenticated(), leave)
router.get('/join', middlewares.isAuthenticated(), require('./clans/join'))
-router.get('/invite-accept', middlewares.isAuthenticated(), require('./clans/inviteAccept'))
+router.get(
+ '/invite-accept',
+ middlewares.isAuthenticated(),
+ require('./clans/inviteAccept')
+)
router.get('*', (req, res) => res.status(503).render('errors/503-known-issue'))
module.exports = router
diff --git a/src/backend/routes/views/clans/create.js b/src/backend/routes/views/clans/create.js
index 1d67926a..b10136f6 100644
--- a/src/backend/routes/views/clans/create.js
+++ b/src/backend/routes/views/clans/create.js
@@ -1,64 +1,84 @@
const { body, validationResult, matchedData } = require('express-validator')
const { JavaApiError } = require('../../../services/ApiErrors')
-module.exports =
- [
- body('clan_tag', 'Please indicate the clan tag - No special characters and 3 characters maximum').notEmpty().isLength({ max: 3 }),
- body('clan_description', 'Please add a description for your clan').notEmpty().isLength({ max: 1000 }),
- body('clan_name', "Please indicate your clan's name").notEmpty().isLength({ max: 40 }),
- async (req, res) => {
- if (req.requestContainer.get('UserService').getUser()?.clan) {
- return res.redirect('/clans/manage')
- }
+module.exports = [
+ body(
+ 'clan_tag',
+ 'Please indicate the clan tag - No special characters and 3 characters maximum'
+ )
+ .notEmpty()
+ .isLength({ max: 3 }),
+ body('clan_description', 'Please add a description for your clan')
+ .notEmpty()
+ .isLength({ max: 1000 }),
+ body('clan_name', "Please indicate your clan's name")
+ .notEmpty()
+ .isLength({ max: 40 }),
+ async (req, res) => {
+ if (req.requestContainer.get('UserService').getUser()?.clan) {
+ return res.redirect('/clans/manage')
+ }
- if (req.method === 'POST') {
- const errors = validationResult(req)
+ if (req.method === 'POST') {
+ const errors = validationResult(req)
- if (!errors.isEmpty()) {
- return res.render('clans/create', {
- errors: {
- class: 'alert-danger',
- messages: errors,
- type: 'Error!'
- },
- clan_tag: req.body.clan_tag,
- clan_name: req.body.clan_name,
- clan_description: req.body.clan_description
- })
- }
-
- try {
- const data = matchedData(req)
- await req.requestContainer.get('ClanManagementService').create(data.clan_tag, data.clan_name, data.clan_description)
+ if (!errors.isEmpty()) {
+ return res.render('clans/create', {
+ errors: {
+ class: 'alert-danger',
+ messages: errors,
+ type: 'Error!',
+ },
+ clan_tag: req.body.clan_tag,
+ clan_name: req.body.clan_name,
+ clan_description: req.body.clan_description,
+ })
+ }
- await req.asyncFlash('info', 'Clan created')
+ try {
+ const data = matchedData(req)
+ await req.requestContainer
+ .get('ClanManagementService')
+ .create(
+ data.clan_tag,
+ data.clan_name,
+ data.clan_description
+ )
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
- } catch (e) {
- let messages = { errors: [{ msg: 'Server-Error while creating the clan' }] }
+ await req.asyncFlash('info', 'Clan created')
- console.error(e.stack)
- if (e instanceof JavaApiError && e.error.errors[0]) {
- messages = { errors: [{ msg: e.error.errors[0].detail }] }
- }
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan
+ .id
+ )
+ } catch (e) {
+ let messages = {
+ errors: [{ msg: 'Server-Error while creating the clan' }],
+ }
- return res.render('clans/create', {
- clan_tag: req.body.clan_tag,
- clan_name: req.body.clan_name,
- clan_description: req.body.clan_description,
- errors: {
- class: 'alert-danger',
- messages,
- type: 'Error!'
- }
- })
+ console.error(e.stack)
+ if (e instanceof JavaApiError && e.error.errors[0]) {
+ messages = { errors: [{ msg: e.error.errors[0].detail }] }
}
- }
- res.render('clans/create', {
- clan_tag: '',
- clan_name: '',
- clan_description: ''
- })
+ return res.render('clans/create', {
+ clan_tag: req.body.clan_tag,
+ clan_name: req.body.clan_name,
+ clan_description: req.body.clan_description,
+ errors: {
+ class: 'alert-danger',
+ messages,
+ type: 'Error!',
+ },
+ })
+ }
}
- ]
+
+ res.render('clans/create', {
+ clan_tag: '',
+ clan_name: '',
+ clan_description: '',
+ })
+ },
+]
diff --git a/src/backend/routes/views/clans/get/clan.js b/src/backend/routes/views/clans/get/clan.js
index c560b5e5..c8cb46a1 100644
--- a/src/backend/routes/views/clans/get/clan.js
+++ b/src/backend/routes/views/clans/get/clan.js
@@ -10,6 +10,8 @@ module.exports = [
const data = matchedData(req)
- return res.render('clans/clan', { clan: await req.appContainer.get('ClanService').getClan(data.id) })
- }
+ return res.render('clans/clan', {
+ clan: await req.appContainer.get('ClanService').getClan(data.id),
+ })
+ },
]
diff --git a/src/backend/routes/views/clans/get/manage.js b/src/backend/routes/views/clans/get/manage.js
index 213ce56d..7c2fcaf1 100755
--- a/src/backend/routes/views/clans/get/manage.js
+++ b/src/backend/routes/views/clans/get/manage.js
@@ -3,17 +3,25 @@ exports = module.exports = async (req, res) => {
// if something changed in another session we should refresh the user first
await req.requestContainer.get('UserService').refreshUser()
- const clanMembershipId = req.requestContainer.get('UserService').getUser()?.clan?.membershipId || null
+ const clanMembershipId =
+ req.requestContainer.get('UserService').getUser()?.clan?.membershipId ||
+ null
if (!clanMembershipId) {
- await req.asyncFlash('error', 'You don\'t belong to a clan')
+ await req.asyncFlash('error', "You don't belong to a clan")
return res.redirect('/clans')
}
try {
- const clan = await req.appContainer.get('ClanService').getClanMembership(clanMembershipId)
+ const clan = await req.appContainer
+ .get('ClanService')
+ .getClanMembership(clanMembershipId)
- return res.render('clans/manage', { clan_description: clan.clan_description, clan_name: clan.clan_name, clan_tag: clan.clan_tag })
+ return res.render('clans/manage', {
+ clan_description: clan.clan_description,
+ clan_name: clan.clan_name,
+ clan_tag: clan.clan_tag,
+ })
} catch (e) {
let message = e.toString()
if (e instanceof JavaApiError && e.error?.errors) {
diff --git a/src/backend/routes/views/clans/invite.js b/src/backend/routes/views/clans/invite.js
index 08fd5a6a..8cfd9a21 100644
--- a/src/backend/routes/views/clans/invite.js
+++ b/src/backend/routes/views/clans/invite.js
@@ -3,29 +3,35 @@ const { body } = require('express-validator')
const url = require('url')
exports = module.exports = [
- body('invited_player', 'Please select a player').notEmpty().isLength({ max: 20 }),
+ body('invited_player', 'Please select a player')
+ .notEmpty()
+ .isLength({ max: 20 }),
async (req, res) => {
if (req.method === 'POST') {
- const user = await req.appContainer.get('DataRepository').fetchUserByName(req.body.invited_player)
+ const user = await req.appContainer
+ .get('DataRepository')
+ .fetchUserByName(req.body.invited_player)
if (!user) {
await req.asyncFlash('error', 'User not found')
return res.render('clans/invite', {
- invited_player: req.body.invited_player
+ invited_player: req.body.invited_player,
})
}
try {
- const invitation = await req.requestContainer.get('ClanManagementService').createInvite(user.id)
+ const invitation = await req.requestContainer
+ .get('ClanManagementService')
+ .createInvite(user.id)
return res.render('clans/invite', {
invited_player: req.body.invited_player,
link: url.format({
pathname: '/clans/invite-accept',
query: {
- token: encodeURIComponent(invitation)
- }
- })
+ token: encodeURIComponent(invitation),
+ },
+ }),
})
} catch (e) {
let message = e.toString()
@@ -40,7 +46,7 @@ exports = module.exports = [
}
return res.render('clans/invite', {
- invited_player: ''
+ invited_player: '',
})
- }
+ },
]
diff --git a/src/backend/routes/views/clans/inviteAccept.js b/src/backend/routes/views/clans/inviteAccept.js
index ab4c56ff..f21a0c6c 100644
--- a/src/backend/routes/views/clans/inviteAccept.js
+++ b/src/backend/routes/views/clans/inviteAccept.js
@@ -1,8 +1,7 @@
const decodingJWT = (token) => {
if (token !== null) {
const base64String = token.split('.')[1]
- return JSON.parse(Buffer.from(base64String,
- 'base64').toString('ascii'))
+ return JSON.parse(Buffer.from(base64String, 'base64').toString('ascii'))
}
return null
}
@@ -26,11 +25,14 @@ exports = module.exports = async function (req, res) {
if (req.requestContainer.get('UserService').getUser()?.clan) {
await req.asyncFlash('error', 'You are already in a clan')
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan.id
+ )
}
res.render('clans/accept_invite', {
acceptURL: `/clans/join?token=${token}`,
- clanName: decodedToken.clan.name
+ clanName: decodedToken.clan.name,
})
}
diff --git a/src/backend/routes/views/clans/join.js b/src/backend/routes/views/clans/join.js
index 02107f93..29b8cf3d 100644
--- a/src/backend/routes/views/clans/join.js
+++ b/src/backend/routes/views/clans/join.js
@@ -9,10 +9,15 @@ exports = module.exports = async function (req, res) {
const token = req.query.token
try {
- await req.requestContainer.get('ClanManagementService').acceptInvitation(token)
+ await req.requestContainer
+ .get('ClanManagementService')
+ .acceptInvitation(token)
await req.asyncFlash('info', 'Clan joined!')
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan.id
+ )
} catch (e) {
console.log(e.stack)
let message = e.toString()
diff --git a/src/backend/routes/views/clans/kick.js b/src/backend/routes/views/clans/kick.js
index 327454b1..083b7f4d 100755
--- a/src/backend/routes/views/clans/kick.js
+++ b/src/backend/routes/views/clans/kick.js
@@ -17,9 +17,14 @@ exports = module.exports = async function (req, res) {
}
try {
- await req.requestContainer.get('ClanManagementService').kickMember(memberId)
-
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
+ await req.requestContainer
+ .get('ClanManagementService')
+ .kickMember(memberId)
+
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan.id
+ )
} catch (e) {
let message = e.toString()
if (e instanceof JavaApiError && e.error?.errors) {
@@ -28,6 +33,9 @@ exports = module.exports = async function (req, res) {
await req.asyncFlash('error', message)
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan.id
+ )
}
}
diff --git a/src/backend/routes/views/clans/post/transfer.js b/src/backend/routes/views/clans/post/transfer.js
index 9d12aa35..59ff29cb 100755
--- a/src/backend/routes/views/clans/post/transfer.js
+++ b/src/backend/routes/views/clans/post/transfer.js
@@ -2,7 +2,7 @@ const flash = {}
const request = require('request')
const { check, validationResult } = require('express-validator')
-function promiseRequest (url) {
+function promiseRequest(url) {
return new Promise(function (resolve, reject) {
request(url, function (error, res, body) {
if (!error && res.statusCode < 300) {
@@ -23,7 +23,10 @@ exports = module.exports = async function (req, res) {
// validate the input
check('transfer_to', 'Please indicate the recipient name').notEmpty()
- check('clan_id', 'Internal error while processing your query: invalid clan ID').notEmpty()
+ check(
+ 'clan_id',
+ 'Internal error while processing your query: invalid clan ID'
+ ).notEmpty()
// check the validation object for errors
const errors = validationResult(req)
@@ -43,12 +46,17 @@ exports = module.exports = async function (req, res) {
const userName = req.body.transfer_to
// Let's check first that the player exists AND is part of this clan
- const fetchRoute = process.env.API_URL + '/data/clan/' + clanId + '?include=memberships.player&fields[player]=login'
+ const fetchRoute =
+ process.env.API_URL +
+ '/data/clan/' +
+ clanId +
+ '?include=memberships.player&fields[player]=login'
let playerId = null
try {
- if (userName === req.user.data.attributes.userName) throw new Error('You cannot transfer your own clan to yourself')
+ if (userName === req.user.data.attributes.userName)
+ throw new Error('You cannot transfer your own clan to yourself')
const httpData = await promiseRequest(fetchRoute)
const clanData = JSON.parse(httpData)
@@ -61,11 +69,22 @@ exports = module.exports = async function (req, res) {
members[record.attributes.login] = record.id
}
- if (!members[userName]) throw new Error('User does not exist or is not part of the clan')
+ if (!members[userName])
+ throw new Error(
+ 'User does not exist or is not part of the clan'
+ )
playerId = members[userName]
} catch (e) {
flash.class = 'alert-danger'
- flash.messages = [{ msg: 'There was an error during the transfer to ' + userName + ': ' + e }]
+ flash.messages = [
+ {
+ msg:
+ 'There was an error during the transfer to ' +
+ userName +
+ ': ' +
+ e,
+ },
+ ]
flash.type = 'Error!'
const buff = Buffer.from(JSON.stringify(flash))
@@ -75,79 +94,94 @@ exports = module.exports = async function (req, res) {
}
// Building update query
- const queryUrl =
- process.env.API_URL +
- '/data/clan/' + clanId
-
- const newClanObject =
- {
- data: {
- type: 'clan',
- id: clanId,
- relationships: {
- leader: {
- data: {
- id: playerId,
- type: 'player'
- }
- }
- }
+ const queryUrl = process.env.API_URL + '/data/clan/' + clanId
+
+ const newClanObject = {
+ data: {
+ type: 'clan',
+ id: clanId,
+ relationships: {
+ leader: {
+ data: {
+ id: playerId,
+ type: 'player',
+ },
+ },
+ },
+ },
}
- }
// Run post to endpoint
- request.patch({
- url: queryUrl,
- body: JSON.stringify(newClanObject),
- headers: {
- Authorization: 'Bearer ' + req.user.data.attributes.token,
- 'Content-Type': 'application/vnd.api+json'
- }
- }, function (err, res, body) {
- if (err || res.statusCode !== 204) {
- const errorMessages = []
- let msg = 'Error during the ownership transfer'
- try {
- msg += ': ' + JSON.stringify(JSON.parse(res.body).errors[0].detail)
- } catch {}
-
- errorMessages.push({ msg })
- flash.class = 'alert-danger'
- flash.messages = errorMessages
- flash.type = 'Error!'
-
- const buff = Buffer.from(JSON.stringify(flash))
- const data = buff.toString('base64')
-
- return overallRes.redirect('manage?flash=' + data)
- } else {
- // Refreshing user
- request.get({
- url: process.env.API_URL + '/me',
- headers: {
- Authorization: 'Bearer ' + req.user.data.attributes.token
- }
+ request.patch(
+ {
+ url: queryUrl,
+ body: JSON.stringify(newClanObject),
+ headers: {
+ Authorization: 'Bearer ' + req.user.data.attributes.token,
+ 'Content-Type': 'application/vnd.api+json',
},
-
- function (err, res, body) {
- if (err) {
- console.error('There was an error updating a session after a clan transfer:', err)
-
- return
- }
+ },
+ function (err, res, body) {
+ if (err || res.statusCode !== 204) {
+ const errorMessages = []
+ let msg = 'Error during the ownership transfer'
try {
- const user = JSON.parse(body)
- user.data.id = user.data.attributes.userId
- user.data.attributes.token = req.user.data.attributes.token
- req.logIn(user, function (err) {
- if (err) console.error(err)
- return overallRes.redirect('see?id=' + clanId)
- })
- } catch {
- console.error('There was an error updating a session after a clan transfer')
- }
- })
+ msg +=
+ ': ' +
+ JSON.stringify(
+ JSON.parse(res.body).errors[0].detail
+ )
+ } catch {}
+
+ errorMessages.push({ msg })
+ flash.class = 'alert-danger'
+ flash.messages = errorMessages
+ flash.type = 'Error!'
+
+ const buff = Buffer.from(JSON.stringify(flash))
+ const data = buff.toString('base64')
+
+ return overallRes.redirect('manage?flash=' + data)
+ } else {
+ // Refreshing user
+ request.get(
+ {
+ url: process.env.API_URL + '/me',
+ headers: {
+ Authorization:
+ 'Bearer ' + req.user.data.attributes.token,
+ },
+ },
+
+ function (err, res, body) {
+ if (err) {
+ console.error(
+ 'There was an error updating a session after a clan transfer:',
+ err
+ )
+
+ return
+ }
+ try {
+ const user = JSON.parse(body)
+ user.data.id = user.data.attributes.userId
+ user.data.attributes.token =
+ req.user.data.attributes.token
+ req.logIn(user, function (err) {
+ if (err) console.error(err)
+ return overallRes.redirect(
+ 'see?id=' + clanId
+ )
+ })
+ } catch {
+ console.error(
+ 'There was an error updating a session after a clan transfer'
+ )
+ }
+ }
+ )
+ }
}
- })
+ )
}
}
diff --git a/src/backend/routes/views/clans/post/update.js b/src/backend/routes/views/clans/post/update.js
index e56c3a2e..12660c1b 100644
--- a/src/backend/routes/views/clans/post/update.js
+++ b/src/backend/routes/views/clans/post/update.js
@@ -2,10 +2,18 @@ const { validationResult, body, matchedData } = require('express-validator')
const { JavaApiError } = require('../../../../services/ApiErrors')
exports = module.exports = [
-
- body('clan_tag', 'Please indicate the clan tag - No special characters and 3 characters maximum').notEmpty().isLength({ max: 3 }),
- body('clan_description', 'Please add a description for your clan').notEmpty().isLength({ max: 1000 }),
- body('clan_name', "Please indicate your clan's name").notEmpty().isLength({ max: 40 }),
+ body(
+ 'clan_tag',
+ 'Please indicate the clan tag - No special characters and 3 characters maximum'
+ )
+ .notEmpty()
+ .isLength({ max: 3 }),
+ body('clan_description', 'Please add a description for your clan')
+ .notEmpty()
+ .isLength({ max: 1000 }),
+ body('clan_name', "Please indicate your clan's name")
+ .notEmpty()
+ .isLength({ max: 40 }),
async (req, res) => {
const errors = validationResult(req)
@@ -15,20 +23,25 @@ exports = module.exports = [
errors: {
class: 'alert-danger',
messages: errors,
- type: 'Error!'
+ type: 'Error!',
},
clan_tag: req.body.clan_tag,
clan_name: req.body.clan_name,
- clan_description: req.body.clan_description
+ clan_description: req.body.clan_description,
})
}
try {
const data = matchedData(req)
- await req.requestContainer.get('ClanManagementService').update(data.clan_tag, data.clan_name, data.clan_description)
+ await req.requestContainer
+ .get('ClanManagementService')
+ .update(data.clan_tag, data.clan_name, data.clan_description)
await req.asyncFlash('info', 'Clan updated')
- return res.redirect('/clans/view/' + req.requestContainer.get('UserService').getUser().clan.id)
+ return res.redirect(
+ '/clans/view/' +
+ req.requestContainer.get('UserService').getUser().clan.id
+ )
} catch (e) {
let message = e.toString()
if (e instanceof JavaApiError && e.error?.errors) {
@@ -40,8 +53,8 @@ exports = module.exports = [
return res.render('clans/manage', {
clan_tag: req.body.clan_tag,
clan_name: req.body.clan_name,
- clan_description: req.body.clan_description
+ clan_description: req.body.clan_description,
})
}
- }
+ },
]
diff --git a/src/backend/routes/views/clans/view.js b/src/backend/routes/views/clans/view.js
index 7e0af591..4caff5ea 100644
--- a/src/backend/routes/views/clans/view.js
+++ b/src/backend/routes/views/clans/view.js
@@ -30,7 +30,13 @@ module.exports = async (req, res) => {
}
}
- return res.render('clans/clan', { clan, isMember, isLeader, canLeave, userMembershipId: user?.clan?.membershipId })
+ return res.render('clans/clan', {
+ clan,
+ isMember,
+ isLeader,
+ canLeave,
+ userMembershipId: user?.clan?.membershipId,
+ })
} catch (e) {
let message = e.toString()
if (e instanceof JavaApiError && e.error?.errors) {
diff --git a/src/backend/routes/views/dataRouter.js b/src/backend/routes/views/dataRouter.js
index b798bba0..e0db535b 100644
--- a/src/backend/routes/views/dataRouter.js
+++ b/src/backend/routes/views/dataRouter.js
@@ -10,7 +10,13 @@ const getData = async (req, res, name, data) => {
return res.status(503).json({ error: 'timeout reached' })
}
- console.error('[error] dataRouter::get:' + name + '.json failed with "' + e.toString() + '"')
+ console.error(
+ '[error] dataRouter::get:' +
+ name +
+ '.json failed with "' +
+ e.toString() +
+ '"'
+ )
if (!res.headersSent) {
return res.status(500).json({ error: 'unexpected error' })
@@ -19,25 +25,47 @@ const getData = async (req, res, name, data) => {
}
}
router.get('/newshub.json', async (req, res) => {
- getData(req, res, 'newshub', await req.appContainer.get('WordpressService').getNewshub())
+ getData(
+ req,
+ res,
+ 'newshub',
+ await req.appContainer.get('WordpressService').getNewshub()
+ )
})
router.get('/tournament-news.json', async (req, res) => {
- getData(req, res, 'tournament-news', await req.appContainer.get('WordpressService').getTournamentNews())
+ getData(
+ req,
+ res,
+ 'tournament-news',
+ await req.appContainer.get('WordpressService').getTournamentNews()
+ )
})
router.get('/faf-teams.json', async (req, res) => {
- getData(req, res, 'faf-teams', await req.appContainer.get('WordpressService').getFafTeams())
+ getData(
+ req,
+ res,
+ 'faf-teams',
+ await req.appContainer.get('WordpressService').getFafTeams()
+ )
})
router.get('/content-creators.json', async (req, res) => {
- getData(req, res, 'content-creators', await req.appContainer.get('WordpressService').getContentCreators())
+ getData(
+ req,
+ res,
+ 'content-creators',
+ await req.appContainer.get('WordpressService').getContentCreators()
+ )
})
router.get('/recent-players.json', async (req, res) => {
- const rawData = await req.appContainer.get('LeaderboardService').getLeaderboard(1)
+ const rawData = await req.appContainer
+ .get('LeaderboardService')
+ .getLeaderboard(1)
const data = rawData.map((item) => ({
id: item.playerId,
- name: item.label
+ name: item.label,
}))
getData(req, res, 'content-creators', data)
@@ -51,7 +79,11 @@ router.get('/clans.json', async (req, res) => {
return res.status(503).json({ error: 'timeout reached' })
}
- console.error('[error] dataRouter::get:clans.json failed with "' + e.toString() + '"')
+ console.error(
+ '[error] dataRouter::get:clans.json failed with "' +
+ e.toString() +
+ '"'
+ )
if (!res.headersSent) {
return res.status(500).json({ error: 'unexpected error' })
diff --git a/src/backend/routes/views/defaultRouter.js b/src/backend/routes/views/defaultRouter.js
index 7a9e2291..a911ff2b 100644
--- a/src/backend/routes/views/defaultRouter.js
+++ b/src/backend/routes/views/defaultRouter.js
@@ -17,10 +17,14 @@ router.get('/clan/:id', (req, res) => {
res.redirect('/clans/view/' + req.params.id)
})
// https://github.com/search?q=org%3AFAForever+account_activated&type=code
-router.get('/account_activated', (req, res) => res.redirect('/account/register'))
+router.get('/account_activated', (req, res) =>
+ res.redirect('/account/register')
+)
// see: https://github.com/search?q=org%3AFAForever%20password_resetted&type=code
-router.get('/password_resetted', (req, res) => res.redirect('/account/requestPasswordReset'))
+router.get('/password_resetted', (req, res) =>
+ res.redirect('/account/requestPasswordReset')
+)
// this is prob. outdated, but don't know
router.get('/report_submitted', require('./account/get/report'))
diff --git a/src/backend/routes/views/leaderboardRouter.js b/src/backend/routes/views/leaderboardRouter.js
index 81b99f6b..03c729ff 100644
--- a/src/backend/routes/views/leaderboardRouter.js
+++ b/src/backend/routes/views/leaderboardRouter.js
@@ -7,7 +7,7 @@ const getLeaderboardId = (leaderboardName) => {
global: 1,
'1v1': 2,
'2v2': 3,
- '4v4': 4
+ '4v4': 4,
}
if (leaderboardName in mapping) {
@@ -26,16 +26,30 @@ router.get('/:leaderboard.json', async (req, res) => {
const leaderboardId = getLeaderboardId(req.params.leaderboard ?? null)
if (leaderboardId === null) {
- return res.status(404).json({ error: 'Leaderboard "' + req.params.leaderboard + '" does not exist' })
+ return res.status(404).json({
+ error:
+ 'Leaderboard "' +
+ req.params.leaderboard +
+ '" does not exist',
+ })
}
- return res.json(await req.appContainer.get('LeaderboardService').getLeaderboard(leaderboardId))
+ return res.json(
+ await req.appContainer
+ .get('LeaderboardService')
+ .getLeaderboard(leaderboardId)
+ )
} catch (e) {
if (e instanceof AcquireTimeoutError) {
return res.status(503).json({ error: 'timeout reached' })
}
- console.error('[error] leaderboardRouter::get:leaderboard.json failed with "' + e.toString() + '"', e.stack)
+ console.error(
+ '[error] leaderboardRouter::get:leaderboard.json failed with "' +
+ e.toString() +
+ '"',
+ e.stack
+ )
if (!res.headersSent) {
return res.status(500).json({ error: 'unexpected error' })
diff --git a/src/backend/routes/views/news.js b/src/backend/routes/views/news.js
index 8cc2f26d..09d81658 100644
--- a/src/backend/routes/views/news.js
+++ b/src/backend/routes/views/news.js
@@ -1,33 +1,42 @@
const express = require('../../ExpressApp')
const router = express.Router()
-function getNewsArticleBySlug (articles, slug) {
- const [newsArticle] = articles.filter((entry) => {
- return entry.slug === slug
- }) ?? []
+function getNewsArticleBySlug(articles, slug) {
+ const [newsArticle] =
+ articles.filter((entry) => {
+ return entry.slug === slug
+ }) ?? []
return newsArticle ?? null
}
-function getNewsArticleByDeprecatedSlug (articles, slug) {
- const [newsArticle] = articles.filter((entry) => {
- return entry.bcSlug === slug
- }) ?? []
+function getNewsArticleByDeprecatedSlug(articles, slug) {
+ const [newsArticle] =
+ articles.filter((entry) => {
+ return entry.bcSlug === slug
+ }) ?? []
return newsArticle ?? null
}
router.get('/', async (req, res) => {
- res.render('news', { news: await req.appContainer.get('WordpressService').getNews() })
+ res.render('news', {
+ news: await req.appContainer.get('WordpressService').getNews(),
+ })
})
router.get('/:slug', async (req, res) => {
- const newsArticles = await req.appContainer.get('WordpressService').getNews()
+ const newsArticles = await req.appContainer
+ .get('WordpressService')
+ .getNews()
const newsArticle = getNewsArticleBySlug(newsArticles, req.params.slug)
if (newsArticle === null) {
- const newsArticleByOldSlug = getNewsArticleByDeprecatedSlug(newsArticles, req.params.slug)
+ const newsArticleByOldSlug = getNewsArticleByDeprecatedSlug(
+ newsArticles,
+ req.params.slug
+ )
if (newsArticleByOldSlug) {
// old slug style, here for backward compatibility
@@ -42,7 +51,7 @@ router.get('/:slug', async (req, res) => {
}
res.render('newsArticle', {
- newsArticle
+ newsArticle,
})
})
diff --git a/src/backend/routes/views/staticMarkdownRouter.js b/src/backend/routes/views/staticMarkdownRouter.js
index 4bea879c..ea219778 100644
--- a/src/backend/routes/views/staticMarkdownRouter.js
+++ b/src/backend/routes/views/staticMarkdownRouter.js
@@ -3,20 +3,37 @@ const showdown = require('showdown')
const fs = require('fs')
const router = express.Router()
-function markdown (template) {
+function markdown(template) {
return (req, res) => {
res.render('markdown', {
- content: new showdown.Converter().makeHtml(fs.readFileSync(template, 'utf-8'))
+ content: new showdown.Converter().makeHtml(
+ fs.readFileSync(template, 'utf-8')
+ ),
})
}
}
-router.get('/privacy', markdown('src/backend/templates/views/markdown/privacy.md'))
-router.get('/privacy-fr', markdown('src/backend/templates/views/markdown/privacy-fr.md'))
-router.get('/privacy-ru', markdown('src/backend/templates/views/markdown/privacy-ru.md'))
+router.get(
+ '/privacy',
+ markdown('src/backend/templates/views/markdown/privacy.md')
+)
+router.get(
+ '/privacy-fr',
+ markdown('src/backend/templates/views/markdown/privacy-fr.md')
+)
+router.get(
+ '/privacy-ru',
+ markdown('src/backend/templates/views/markdown/privacy-ru.md')
+)
router.get('/tos', markdown('src/backend/templates/views/markdown/tos.md'))
-router.get('/tos-fr', markdown('src/backend/templates/views/markdown/tos-fr.md'))
-router.get('/tos-ru', markdown('src/backend/templates/views/markdown/tos-ru.md'))
+router.get(
+ '/tos-fr',
+ markdown('src/backend/templates/views/markdown/tos-fr.md')
+)
+router.get(
+ '/tos-ru',
+ markdown('src/backend/templates/views/markdown/tos-ru.md')
+)
router.get('/rules', markdown('src/backend/templates/views/markdown/rules.md'))
router.get('/cg', markdown('src/backend/templates/views/markdown/cg.md'))
diff --git a/src/backend/security/bootPassport.js b/src/backend/security/bootPassport.js
index 1dd12e81..766738ba 100644
--- a/src/backend/security/bootPassport.js
+++ b/src/backend/security/bootPassport.js
@@ -12,32 +12,60 @@ module.exports.bootPassport = (expressApp, appConfig) => {
passport.serializeUser((user, done) => done(null, user))
passport.deserializeUser((user, done) => done(null, user))
- const authStrategy = new OidcStrategy({
- passReqToCallback: true,
- issuer: appConfig.oauth.url + '/',
- tokenURL: appConfig.oauth.url + '/oauth2/token',
- authorizationURL: appConfig.oauth.publicUrl + '/oauth2/auth',
- userInfoURL: appConfig.oauth.url + '/userinfo?schema=openid',
- clientID: appConfig.oauth.clientId,
- clientSecret: appConfig.oauth.clientSecret,
- callbackURL: `${appConfig.host}/${appConfig.oauth.callback}`,
- scope: ['openid', 'offline', 'public_profile', 'write_account_data']
- }, async function (req, iss, sub, profile, jwtClaims, token, refreshToken, params, verified) {
- const oAuthPassport = {
+ const authStrategy = new OidcStrategy(
+ {
+ passReqToCallback: true,
+ issuer: appConfig.oauth.url + '/',
+ tokenURL: appConfig.oauth.url + '/oauth2/token',
+ authorizationURL: appConfig.oauth.publicUrl + '/oauth2/auth',
+ userInfoURL: appConfig.oauth.url + '/userinfo?schema=openid',
+ clientID: appConfig.oauth.clientId,
+ clientSecret: appConfig.oauth.clientSecret,
+ callbackURL: `${appConfig.host}/${appConfig.oauth.callback}`,
+ scope: [
+ 'openid',
+ 'offline',
+ 'public_profile',
+ 'write_account_data',
+ ],
+ },
+ async function (
+ req,
+ iss,
+ sub,
+ profile,
+ jwtClaims,
token,
- refreshToken
- }
+ refreshToken,
+ params,
+ verified
+ ) {
+ const oAuthPassport = {
+ token,
+ refreshToken,
+ }
- const apiClient = JavaApiClientFactory.createInstance(new UserService(), appConfig.apiUrl, oAuthPassport)
- const userRepository = new UserRepository(apiClient)
+ const apiClient = JavaApiClientFactory.createInstance(
+ new UserService(),
+ appConfig.apiUrl,
+ oAuthPassport
+ )
+ const userRepository = new UserRepository(apiClient)
- userRepository.fetchUser(oAuthPassport).then(user => {
- verified(null, user)
- }).catch(e => {
- console.error('[Error] oAuth verify failed with "' + e.toString() + '"')
- verified(null, null)
- })
- }
+ userRepository
+ .fetchUser(oAuthPassport)
+ .then((user) => {
+ verified(null, user)
+ })
+ .catch((e) => {
+ console.error(
+ '[Error] oAuth verify failed with "' +
+ e.toString() +
+ '"'
+ )
+ verified(null, null)
+ })
+ }
)
passport.use(appConfig.oauth.strategy, authStrategy)
diff --git a/src/backend/services/ApiErrors.js b/src/backend/services/ApiErrors.js
index bd73857c..8bdbdafd 100644
--- a/src/backend/services/ApiErrors.js
+++ b/src/backend/services/ApiErrors.js
@@ -1,5 +1,5 @@
class JavaApiError extends Error {
- constructor (status, url, error) {
+ constructor(status, url, error) {
super('Failed request "' + url + '" with status "' + status + '"')
this.status = status
diff --git a/src/backend/services/CacheService.js b/src/backend/services/CacheService.js
index a132f241..ab899b5b 100644
--- a/src/backend/services/CacheService.js
+++ b/src/backend/services/CacheService.js
@@ -1,9 +1,7 @@
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
- }
-)
+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 = cacheService
diff --git a/src/backend/services/ClanManagementRepository.js b/src/backend/services/ClanManagementRepository.js
index 23bbd8b5..b581bad0 100644
--- a/src/backend/services/ClanManagementRepository.js
+++ b/src/backend/services/ClanManagementRepository.js
@@ -1,19 +1,28 @@
const { JavaApiError, GenericJavaApiError } = require('./ApiErrors')
class ClanManagementRepository {
- constructor (javaApiClient) {
+ constructor(javaApiClient) {
this.javaApiClient = javaApiClient
}
- async create (tag, name, description) {
+ async create(tag, name, description) {
try {
- const response = await this.javaApiClient.post('/clans/create' +
- '?name=' + encodeURIComponent(name) +
- '&tag=' + encodeURIComponent(tag) +
- '&description=' + encodeURIComponent(description))
+ const response = await this.javaApiClient.post(
+ '/clans/create' +
+ '?name=' +
+ encodeURIComponent(name) +
+ '&tag=' +
+ encodeURIComponent(tag) +
+ '&description=' +
+ encodeURIComponent(description)
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
} catch (e) {
if (e instanceof JavaApiError) {
@@ -24,15 +33,19 @@ class ClanManagementRepository {
}
}
- async deleteClan (clanId) {
+ async deleteClan(clanId) {
const response = await this.javaApiClient.delete('/data/clan/' + clanId)
if (response.status !== 204) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
}
- async update (id, tag, name, description) {
+ async update(id, tag, name, description) {
const newClanObject = {
data: {
type: 'clan',
@@ -40,21 +53,29 @@ class ClanManagementRepository {
attributes: {
description,
name,
- tag
- }
- }
+ tag,
+ },
+ },
}
try {
- const response = await this.javaApiClient.patch(`/data/clan/${id}`, JSON.stringify(newClanObject), {
- headers: {
- 'Content-Type': 'application/vnd.api+json',
- Accept: 'application/vnd.api+json'
+ const response = await this.javaApiClient.patch(
+ `/data/clan/${id}`,
+ JSON.stringify(newClanObject),
+ {
+ headers: {
+ 'Content-Type': 'application/vnd.api+json',
+ Accept: 'application/vnd.api+json',
+ },
}
- })
+ )
if (response.status !== 204) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
} catch (e) {
if (e instanceof JavaApiError) {
@@ -65,12 +86,18 @@ class ClanManagementRepository {
}
}
- async createInvite (clanId, playerId) {
+ async createInvite(clanId, playerId) {
try {
- const response = await this.javaApiClient.get(`/clans/generateInvitationLink?clanId=${clanId}&playerId=${playerId}`)
+ const response = await this.javaApiClient.get(
+ `/clans/generateInvitationLink?clanId=${clanId}&playerId=${playerId}`
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
const rawToken = JSON.parse(response.data)
@@ -85,12 +112,18 @@ class ClanManagementRepository {
}
}
- async acceptInvitation (token) {
+ async acceptInvitation(token) {
try {
- const response = await this.javaApiClient.post(`/clans/joinClan?token=${token}`)
+ const response = await this.javaApiClient.post(
+ `/clans/joinClan?token=${token}`
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
} catch (e) {
if (e instanceof JavaApiError) {
@@ -101,12 +134,18 @@ class ClanManagementRepository {
}
}
- async removeClanMembership (membershipId) {
+ async removeClanMembership(membershipId) {
try {
- const response = await this.javaApiClient.delete(`/data/clanMembership/${membershipId}`)
+ const response = await this.javaApiClient.delete(
+ `/data/clanMembership/${membershipId}`
+ )
if (response.status !== 204) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
} catch (e) {
if (e instanceof JavaApiError) {
diff --git a/src/backend/services/ClanManagementService.js b/src/backend/services/ClanManagementService.js
index 0dd3daf1..ffcc1f55 100644
--- a/src/backend/services/ClanManagementService.js
+++ b/src/backend/services/ClanManagementService.js
@@ -1,31 +1,42 @@
class ClanManagementService {
- constructor (userService, clanManagementRepository, clanService) {
+ constructor(userService, clanManagementRepository, clanService) {
this.userService = userService
this.clanManagementRepository = clanManagementRepository
this.clanService = clanService
}
- async create (tag, name, description) {
+ async create(tag, name, description) {
await this.clanManagementRepository.create(tag, name, description)
try {
- this.clanService.getAll(true).then(() => {}).catch((e) => console.error(e.stack))
+ this.clanService
+ .getAll(true)
+ .then(() => {})
+ .catch((e) => console.error(e.stack))
await this.userService.refreshUser()
} catch (e) {
console.error(e.stack)
}
}
- async update (tag, name, description) {
- await this.clanManagementRepository.update(this.userService.getUser().clan.id, tag, name, description)
+ async update(tag, name, description) {
+ await this.clanManagementRepository.update(
+ this.userService.getUser().clan.id,
+ tag,
+ name,
+ description
+ )
try {
- this.clanService.getAll(true).then(() => {}).catch((e) => console.error(e.stack))
+ this.clanService
+ .getAll(true)
+ .then(() => {})
+ .catch((e) => console.error(e.stack))
await this.userService.refreshUser()
} catch (e) {
console.error(e.stack)
}
}
- async deleteClan () {
+ async deleteClan() {
const clanId = parseInt(this.userService.getUser()?.clan.id)
if (!clanId) {
throw new Error('User has no clan to destroy')
@@ -35,18 +46,24 @@ class ClanManagementService {
// update user and caches, may this should be an event and not directly called here
try {
- this.clanService.getAll(true).then(() => {}).catch((e) => console.error(e.stack))
+ this.clanService
+ .getAll(true)
+ .then(() => {})
+ .catch((e) => console.error(e.stack))
await this.userService.refreshUser()
} catch (e) {
console.error(e.stack)
}
}
- async createInvite (playerId) {
- return await this.clanManagementRepository.createInvite(this.userService.getUser().clan.id, playerId)
+ async createInvite(playerId) {
+ return await this.clanManagementRepository.createInvite(
+ this.userService.getUser().clan.id,
+ playerId
+ )
}
- async acceptInvitation (token) {
+ async acceptInvitation(token) {
await this.clanManagementRepository.acceptInvitation(token)
try {
await this.userService.refreshUser()
@@ -55,12 +72,14 @@ class ClanManagementService {
}
}
- async kickMember (membershipId) {
+ async kickMember(membershipId) {
await this.clanManagementRepository.removeClanMembership(membershipId)
}
- async leaveClan () {
- await this.clanManagementRepository.removeClanMembership(this.userService.getUser().clan.membershipId)
+ async leaveClan() {
+ await this.clanManagementRepository.removeClanMembership(
+ this.userService.getUser().clan.membershipId
+ )
try {
await this.userService.refreshUser()
} catch (e) {
diff --git a/src/backend/services/ClanService.js b/src/backend/services/ClanService.js
index f95b345c..f7078524 100644
--- a/src/backend/services/ClanService.js
+++ b/src/backend/services/ClanService.js
@@ -2,22 +2,22 @@ const { MutexService } = require('./MutexService')
const clanTTL = 60 * 60
class ClanService {
- constructor (cacheService, dataRepository, lockTimeout = 3000) {
+ constructor(cacheService, dataRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.mutexService = new MutexService()
this.dataRepository = dataRepository
}
- getCacheKey (name) {
+ getCacheKey(name) {
return 'ClanService_' + name
}
- async getClan (id) {
+ async getClan(id) {
return await this.dataRepository.fetchClan(id)
}
- async getAll (ignoreCache = false) {
+ async getAll(ignoreCache = false) {
const cacheKey = this.getCacheKey('all')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -25,8 +25,7 @@ class ClanService {
}
if (this.mutexService.locked) {
- await this.mutexService.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexService.acquire(() => {}, this.lockTimeout)
return this.getAll()
}
@@ -38,7 +37,7 @@ class ClanService {
return this.getAll()
}
- async getClanMembership (clanMembershipId) {
+ async getClanMembership(clanMembershipId) {
return this.dataRepository.fetchClanMembership(clanMembershipId)
}
}
diff --git a/src/backend/services/DataRepository.js b/src/backend/services/DataRepository.js
index 3cd48732..11a83ba4 100644
--- a/src/backend/services/DataRepository.js
+++ b/src/backend/services/DataRepository.js
@@ -1,25 +1,35 @@
const { JavaApiError } = require('./ApiErrors')
class DataRepository {
- constructor (javaApiM2MClient) {
+ constructor(javaApiM2MClient) {
this.javaApiM2MClient = javaApiM2MClient
}
- async fetchAllClans () {
- const response = await this.javaApiM2MClient.get('/data/clan?include=leader&fields[clan]=name,tag,description,leader,memberships,createTime&fields[player]=login&page[number]=1&page[size]=3000')
+ async fetchAllClans() {
+ const response = await this.javaApiM2MClient.get(
+ '/data/clan?include=leader&fields[clan]=name,tag,description,leader,memberships,createTime&fields[player]=login&page[number]=1&page[size]=3000'
+ )
if (response.status !== 200) {
- throw new Error('DataRepository::fetchAllClans failed with response status "' + response.status + '"')
+ throw new Error(
+ 'DataRepository::fetchAllClans failed with response status "' +
+ response.status +
+ '"'
+ )
}
const responseData = JSON.parse(response.data)
if (typeof responseData !== 'object' || responseData === null) {
- throw new Error('DataRepository::fetchAllClans malformed response, not an object')
+ throw new Error(
+ 'DataRepository::fetchAllClans malformed response, not an object'
+ )
}
if (!Object.prototype.hasOwnProperty.call(responseData, 'data')) {
- throw new Error('DataRepository::fetchAllClans malformed response, expected "data"')
+ throw new Error(
+ 'DataRepository::fetchAllClans malformed response, expected "data"'
+ )
}
if (responseData.data.length === 0) {
@@ -29,7 +39,9 @@ class DataRepository {
}
if (!Object.prototype.hasOwnProperty.call(responseData, 'included')) {
- throw new Error('DataRepository::fetchAll malformed response, expected "included"')
+ throw new Error(
+ 'DataRepository::fetchAll malformed response, expected "included"'
+ )
}
const clans = responseData.data.map((item, index) => ({
@@ -39,7 +51,7 @@ class DataRepository {
tag: item.attributes.tag,
createTime: item.attributes.createTime,
description: item.attributes.description,
- population: item.relationships.memberships.data.length
+ population: item.relationships.memberships.data.length,
}))
clans.sort((a, b) => {
@@ -56,21 +68,31 @@ class DataRepository {
return await clans
}
- async fetchClan (id) {
- const response = await this.javaApiM2MClient.get(`/data/clan/${id}?include=memberships.player`)
+ async fetchClan(id) {
+ const response = await this.javaApiM2MClient.get(
+ `/data/clan/${id}?include=memberships.player`
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
const data = JSON.parse(response.data)
if (typeof data !== 'object' || data === null) {
- throw new Error('DataRepository::fetchClan malformed response, not an object')
+ throw new Error(
+ 'DataRepository::fetchClan malformed response, not an object'
+ )
}
if (!Object.prototype.hasOwnProperty.call(data, 'data')) {
- throw new Error('DataRepository::fetchClan malformed response, expected "data"')
+ throw new Error(
+ 'DataRepository::fetchClan malformed response, expected "data"'
+ )
}
if (typeof data.data !== 'object' || data.data === null) {
@@ -78,7 +100,9 @@ class DataRepository {
}
if (typeof data.included !== 'object' || data.included === null) {
- throw new Error('DataRepository::fetchClan malformed response, expected "included"')
+ throw new Error(
+ 'DataRepository::fetchClan malformed response, expected "included"'
+ )
}
const clanRaw = data.data.attributes
@@ -94,12 +118,13 @@ class DataRepository {
updateTime: clanRaw.updateTime,
founder: null,
leader: null,
- memberships: {}
+ memberships: {},
}
const members = {}
for (const k in data.included) {
+ // prettier-ignore
switch (data.included[k].type) {
case 'player': {
const player = data.included[k]
@@ -134,23 +159,33 @@ class DataRepository {
return clan
}
- async fetchClanMembership (clanMembershipId) {
- const response = await this.javaApiM2MClient.get(`/data/clanMembership/${clanMembershipId}/clan?include=memberships.player&fields[clan]=createTime,description,name,tag,updateTime,websiteUrl,founder,leader&fields[player]=login,updateTime&fields[clanMembership]=createTime,player`)
+ async fetchClanMembership(clanMembershipId) {
+ const response = await this.javaApiM2MClient.get(
+ `/data/clanMembership/${clanMembershipId}/clan?include=memberships.player&fields[clan]=createTime,description,name,tag,updateTime,websiteUrl,founder,leader&fields[player]=login,updateTime&fields[clanMembership]=createTime,player`
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
return this.mapClanMembership(JSON.parse(response.data))
}
- mapClanMembership (data) {
+ mapClanMembership(data) {
if (typeof data !== 'object' || data === null) {
- throw new Error('ClanRepository::mapClanMembership malformed response, not an object')
+ throw new Error(
+ 'ClanRepository::mapClanMembership malformed response, not an object'
+ )
}
if (!Object.prototype.hasOwnProperty.call(data, 'data')) {
- throw new Error('ClanRepository::mapClanMembership malformed response, expected "data"')
+ throw new Error(
+ 'ClanRepository::mapClanMembership malformed response, expected "data"'
+ )
}
if (typeof data.data !== 'object' || data.data === null) {
@@ -158,7 +193,9 @@ class DataRepository {
}
if (typeof data.included !== 'object' || data.included === null) {
- throw new Error('ClanRepository::mapClanMembership malformed response, expected "included"')
+ throw new Error(
+ 'ClanRepository::mapClanMembership malformed response, expected "included"'
+ )
}
const clanMembershipRaw = data.data.attributes
@@ -168,12 +205,13 @@ class DataRepository {
clan_name: clanMembershipRaw.name,
clan_tag: clanMembershipRaw.tag,
clan_description: clanMembershipRaw.description,
- clan_create_time: clanMembershipRaw.createTime
+ clan_create_time: clanMembershipRaw.createTime,
}
const members = {}
for (const k in data.included) {
+ // prettier-ignore
switch (data.included[k].type) {
case 'player': {
const player = data.included[k]
@@ -200,11 +238,17 @@ class DataRepository {
return clanMembership
}
- async fetchUserByName (userName) {
- const response = await this.javaApiM2MClient.get(`/data/player?filter=login==${userName}&fields[player]=`)
+ async fetchUserByName(userName) {
+ const response = await this.javaApiM2MClient.get(
+ `/data/player?filter=login==${userName}&fields[player]=`
+ )
if (response.status !== 200) {
- throw new JavaApiError(response.status, response.config.url, JSON.parse(response.data) || [])
+ throw new JavaApiError(
+ response.status,
+ response.config.url,
+ JSON.parse(response.data) || []
+ )
}
const rawUser = JSON.parse(response.data)
@@ -214,7 +258,7 @@ class DataRepository {
}
return {
- id: rawUser.data[0].id
+ id: rawUser.data[0].id,
}
}
}
diff --git a/src/backend/services/JavaApiClientFactory.js b/src/backend/services/JavaApiClientFactory.js
index 5e749dfd..2f096cc8 100644
--- a/src/backend/services/JavaApiClientFactory.js
+++ b/src/backend/services/JavaApiClientFactory.js
@@ -4,18 +4,29 @@ const { AuthFailed } = require('./ApiErrors')
const getRefreshToken = (strategy, oAuthPassport) => {
return new Promise((resolve, reject) => {
- refresh.requestNewAccessToken(strategy, oAuthPassport.refreshToken, function (err, accessToken, refreshToken) {
- if (err || !accessToken || !refreshToken) {
- return reject(new AuthFailed('Failed to refresh token' + err))
- }
+ refresh.requestNewAccessToken(
+ strategy,
+ oAuthPassport.refreshToken,
+ function (err, accessToken, refreshToken) {
+ if (err || !accessToken || !refreshToken) {
+ return reject(
+ new AuthFailed('Failed to refresh token' + err)
+ )
+ }
- return resolve([accessToken, refreshToken])
- })
+ return resolve([accessToken, refreshToken])
+ }
+ )
})
}
class JavaApiClientFactory {
- static createInstance (userService, javaApiBaseURL, oAuthPassport, strategy) {
+ static createInstance(
+ userService,
+ javaApiBaseURL,
+ oAuthPassport,
+ strategy
+ ) {
if (typeof oAuthPassport !== 'object') {
throw new Error('oAuthPassport not an object')
}
@@ -30,22 +41,28 @@ class JavaApiClientFactory {
let tokenRefreshRunning = null
const client = new Axios({
- baseURL: javaApiBaseURL
+ baseURL: javaApiBaseURL,
})
- client.interceptors.request.use(
- async config => {
- config.headers.Authorization = `Bearer ${oAuthPassport.token}`
+ client.interceptors.request.use(async (config) => {
+ config.headers.Authorization = `Bearer ${oAuthPassport.token}`
- return config
- })
+ return config
+ })
client.interceptors.response.use((res) => {
- if (!res.config._refreshTokenRequest && res.config && res.status === 401) {
+ if (
+ !res.config._refreshTokenRequest &&
+ res.config &&
+ res.status === 401
+ ) {
res.config._refreshTokenRequest = true
if (!tokenRefreshRunning) {
- tokenRefreshRunning = getRefreshToken(strategy, oAuthPassport)
+ tokenRefreshRunning = getRefreshToken(
+ strategy,
+ oAuthPassport
+ )
}
return tokenRefreshRunning.then(([token, refreshToken]) => {
@@ -59,7 +76,9 @@ class JavaApiClientFactory {
}
if (res.status === 401) {
- throw new AuthFailed('Token no longer valid and refresh did not help')
+ throw new AuthFailed(
+ 'Token no longer valid and refresh did not help'
+ )
}
return res
diff --git a/src/backend/services/JavaApiM2MClient.js b/src/backend/services/JavaApiM2MClient.js
index 94e7c22e..690c3e28 100644
--- a/src/backend/services/JavaApiM2MClient.js
+++ b/src/backend/services/JavaApiM2MClient.js
@@ -3,15 +3,19 @@ const { ClientCredentials } = require('simple-oauth2')
const { AuthFailed } = require('./ApiErrors')
class JavaApiM2MClient {
- static createInstance (clientId, clientSecret, host, javaApiBaseURL) {
+ static createInstance(clientId, clientSecret, host, javaApiBaseURL) {
let passport = null
const axios = new Axios({
- baseURL: javaApiBaseURL
+ baseURL: javaApiBaseURL,
})
axios.interceptors.request.use(async (config) => {
if (!passport || passport.expired()) {
- passport = await JavaApiM2MClient.getToken(clientId, clientSecret, host)
+ passport = await JavaApiM2MClient.getToken(
+ clientId,
+ clientSecret,
+ host
+ )
}
config.headers.Authorization = `Bearer ${passport.token.access_token}`
@@ -21,26 +25,25 @@ class JavaApiM2MClient {
return axios
}
- static getToken (clientId, clientSecret, host) {
+ static getToken(clientId, clientSecret, host) {
const tokenClient = new ClientCredentials({
client: {
id: clientId,
- secret: clientSecret
-
+ secret: clientSecret,
},
auth: {
tokenHost: host,
tokenPath: '/oauth2/token',
- revokePath: '/oauth2/revoke'
+ revokePath: '/oauth2/revoke',
},
options: {
- authorizationMethod: 'body'
- }
+ authorizationMethod: 'body',
+ },
})
try {
return tokenClient.getToken({
- scope: ''
+ scope: '',
})
} catch (error) {
throw new AuthFailed(error.toString())
diff --git a/src/backend/services/LeaderboardRepository.js b/src/backend/services/LeaderboardRepository.js
index 8b1ab87d..d6a29d63 100644
--- a/src/backend/services/LeaderboardRepository.js
+++ b/src/backend/services/LeaderboardRepository.js
@@ -1,35 +1,45 @@
class LeaderboardRepository {
- constructor (javaApiClient, monthsInThePast = 12) {
+ constructor(javaApiClient, monthsInThePast = 12) {
this.javaApiClient = javaApiClient
this.monthsInThePast = monthsInThePast
}
- getUpdateTimeForApiEntries () {
+ getUpdateTimeForApiEntries() {
const date = new Date()
date.setMonth(date.getMonth() - this.monthsInThePast)
return date.toISOString()
}
- async fetchLeaderboard (id) {
+ async fetchLeaderboard(id) {
const updateTime = this.getUpdateTimeForApiEntries()
- const response = await this.javaApiClient.get(`/data/leaderboardRating?include=player&sort=-rating&filter=leaderboard.id==${id};updateTime=ge=${updateTime}&page[size]=9999`)
+ const response = await this.javaApiClient.get(
+ `/data/leaderboardRating?include=player&sort=-rating&filter=leaderboard.id==${id};updateTime=ge=${updateTime}&page[size]=9999`
+ )
if (response.status !== 200) {
- throw new Error('LeaderboardRepository::fetchLeaderboard failed with response status "' + response.status + '"')
+ throw new Error(
+ 'LeaderboardRepository::fetchLeaderboard failed with response status "' +
+ response.status +
+ '"'
+ )
}
return this.mapResponse(JSON.parse(response.data))
}
- mapResponse (data) {
+ mapResponse(data) {
if (typeof data !== 'object' || data === null) {
- throw new Error('LeaderboardRepository::mapResponse malformed response, not an object')
+ throw new Error(
+ 'LeaderboardRepository::mapResponse malformed response, not an object'
+ )
}
if (!Object.prototype.hasOwnProperty.call(data, 'data')) {
- throw new Error('LeaderboardRepository::mapResponse malformed response, expected "data"')
+ throw new Error(
+ 'LeaderboardRepository::mapResponse malformed response, expected "data"'
+ )
}
if (data.data.length === 0) {
@@ -39,7 +49,9 @@ class LeaderboardRepository {
}
if (!Object.prototype.hasOwnProperty.call(data, 'included')) {
- throw new Error('LeaderboardRepository::mapResponse malformed response, expected "included"')
+ throw new Error(
+ 'LeaderboardRepository::mapResponse malformed response, expected "included"'
+ )
}
const leaderboardData = []
@@ -52,10 +64,16 @@ class LeaderboardRepository {
totalgames: item.attributes.totalGames,
wonGames: item.attributes.wonGames,
date: item.attributes.updateTime,
- label: data.included[index]?.attributes.login || 'unknown user'
+ label:
+ data.included[index]?.attributes.login ||
+ 'unknown user',
})
} catch (e) {
- console.error('LeaderboardRepository::mapResponse failed on item with "' + e.toString() + '"')
+ console.error(
+ 'LeaderboardRepository::mapResponse failed on item with "' +
+ e.toString() +
+ '"'
+ )
}
})
diff --git a/src/backend/services/LeaderboardService.js b/src/backend/services/LeaderboardService.js
index 0654e218..1a913a9e 100644
--- a/src/backend/services/LeaderboardService.js
+++ b/src/backend/services/LeaderboardService.js
@@ -2,16 +2,18 @@ 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, leaderboardRepository, lockTimeout = 3000) {
+ constructor(cacheService, leaderboardRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.mutexService = new MutexService()
this.leaderboardRepository = leaderboardRepository
}
- async getLeaderboard (id, ignoreCache = false) {
- if (typeof (id) !== 'number') {
- throw new Error('LeaderboardService:getLeaderboard id must be a number')
+ async getLeaderboard(id, ignoreCache = false) {
+ if (typeof id !== 'number') {
+ throw new Error(
+ 'LeaderboardService:getLeaderboard id must be a number'
+ )
}
const cacheKey = 'leaderboard-' + id
@@ -21,8 +23,7 @@ class LeaderboardService {
}
if (this.mutexService.locked) {
- await this.mutexService.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexService.acquire(() => {}, this.lockTimeout)
return this.getLeaderboard(id)
}
diff --git a/src/backend/services/MutexService.js b/src/backend/services/MutexService.js
index a7e1cbad..e131558b 100644
--- a/src/backend/services/MutexService.js
+++ b/src/backend/services/MutexService.js
@@ -1,13 +1,12 @@
-class AcquireTimeoutError extends Error {
-}
+class AcquireTimeoutError extends Error {}
class MutexService {
- constructor () {
+ constructor() {
this.queue = []
this.locked = false
}
- async acquire (callback, timeLimitMS = 500) {
+ async acquire(callback, timeLimitMS = 500) {
let timeoutHandle
const lockHandler = {}
@@ -16,7 +15,10 @@ class MutexService {
lockHandler.reject = reject
timeoutHandle = setTimeout(
- () => reject(new AcquireTimeoutError('MutexService timeout reached')),
+ () =>
+ reject(
+ new AcquireTimeoutError('MutexService timeout reached')
+ ),
timeLimitMS
)
})
@@ -33,30 +35,32 @@ class MutexService {
}
})
- await Promise.race([asyncPromise, timeoutPromise]).then(async () => {
- clearTimeout(timeoutHandle)
- try {
- if (callback[Symbol.toStringTag] === 'AsyncFunction') {
- await callback()
- return
- }
+ await Promise.race([asyncPromise, timeoutPromise])
+ .then(async () => {
+ clearTimeout(timeoutHandle)
+ try {
+ if (callback[Symbol.toStringTag] === 'AsyncFunction') {
+ await callback()
+ return
+ }
- callback()
- } finally {
- this.release()
- }
- }).catch(e => {
- const index = this.queue.indexOf(lockHandler)
+ callback()
+ } finally {
+ this.release()
+ }
+ })
+ .catch((e) => {
+ const index = this.queue.indexOf(lockHandler)
- if (index !== -1) {
- this.queue.splice(index, 1)
- }
+ if (index !== -1) {
+ this.queue.splice(index, 1)
+ }
- throw e
- })
+ throw e
+ })
}
- release () {
+ release() {
if (this.queue.length > 0) {
const queueItem = this.queue.shift()
queueItem.resolve()
diff --git a/src/backend/services/Scheduler.js b/src/backend/services/Scheduler.js
index 67a7dd59..6ff58e67 100644
--- a/src/backend/services/Scheduler.js
+++ b/src/backend/services/Scheduler.js
@@ -1,7 +1,7 @@
const { EventEmitter } = require('events')
module.exports = class Scheduler extends EventEmitter {
- constructor (eventName, action, ms) {
+ constructor(eventName, action, ms) {
super()
this.eventName = eventName
this.action = action
@@ -10,9 +10,12 @@ module.exports = class Scheduler extends EventEmitter {
this.addListener(this.eventName, this.action)
}
- start () {
+ start() {
if (!this.handle) {
- this.handle = setInterval(() => this.emit(this.eventName), this.interval)
+ this.handle = setInterval(
+ () => this.emit(this.eventName),
+ this.interval
+ )
}
}
}
diff --git a/src/backend/services/UserRepository.js b/src/backend/services/UserRepository.js
index 24b02ca0..a1d8a48f 100644
--- a/src/backend/services/UserRepository.js
+++ b/src/backend/services/UserRepository.js
@@ -1,10 +1,10 @@
class UserRepository {
- constructor (javaApiClient) {
+ constructor(javaApiClient) {
this.javaApiClient = javaApiClient
}
- fetchUser (oAuthPassport) {
- return this.javaApiClient.get('/me').then(response => {
+ fetchUser(oAuthPassport) {
+ return this.javaApiClient.get('/me').then((response) => {
const rawUser = JSON.parse(response.data).data
let clan = null
@@ -12,9 +12,11 @@ class UserRepository {
if (rawUser.attributes.clan) {
clan = {
id: parseInt(rawUser.attributes.clan.id),
- membershipId: parseInt(rawUser.attributes.clan.membershipId),
+ membershipId: parseInt(
+ rawUser.attributes.clan.membershipId
+ ),
tag: rawUser.attributes.clan.tag,
- name: rawUser.attributes.clan.name
+ name: rawUser.attributes.clan.name,
}
}
@@ -23,7 +25,7 @@ class UserRepository {
name: rawUser.attributes.userName,
email: rawUser.attributes.email,
clan,
- oAuthPassport
+ oAuthPassport,
}
})
}
diff --git a/src/backend/services/UserService.js b/src/backend/services/UserService.js
index 1c59ed5b..e0eb2061 100644
--- a/src/backend/services/UserService.js
+++ b/src/backend/services/UserService.js
@@ -1,39 +1,39 @@
class UserService {
- constructor () {
+ constructor() {
this.user = null
this.session = null
this.userRepository = null
}
- setUserFromRequest (request) {
+ setUserFromRequest(request) {
this.user = request.user
this.session = request.session
}
- setUserRepository (userRepository) {
+ setUserRepository(userRepository) {
this.userRepository = userRepository
}
- isAuthenticated () {
+ isAuthenticated() {
return !!this.user
}
- getUser () {
+ getUser() {
return this.session?.passport?.user
}
- updatePassport (oAuthPassport) {
+ updatePassport(oAuthPassport) {
this.user.oAuthPassport = oAuthPassport
this.session.passport.user.oAuthPassport = oAuthPassport
}
- async refreshUser () {
+ async refreshUser() {
const oAuthPassport = this.user.oAuthPassport
this.user = await this.userRepository.fetchUser(oAuthPassport)
this.session.passport.user = this.user
- await new Promise(resolve => this.session.save(resolve))
+ await new Promise((resolve) => this.session.save(resolve))
return this.user
}
diff --git a/src/backend/services/WordpressRepository.js b/src/backend/services/WordpressRepository.js
index 4fcb6014..072a0fc1 100644
--- a/src/backend/services/WordpressRepository.js
+++ b/src/backend/services/WordpressRepository.js
@@ -1,88 +1,120 @@
const { convert } = require('url-slug')
class WordpressRepository {
- constructor (wordpressClient) {
+ constructor(wordpressClient) {
this.wordpressClient = wordpressClient
}
- async fetchNews () {
- const 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')
+ async fetchNews() {
+ const 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'
+ )
if (response.status !== 200) {
- throw new Error('WordpressRepository::fetchNews failed with response status "' + response.status + '"')
+ 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')
+ throw new Error(
+ 'WordpressRepository::mapNewsResponse malformed response, not an object'
+ )
}
- return rawNewsData.map(item => ({
+ 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
+ media: item._embedded['wp:featuredmedia'][0].source_url,
}))
}
- async fetchTournamentNews () {
- const response = await this.wordpressClient.get('/wp-json/wp/v2/posts/?per_page=10&_embed&_fields=content.rendered,categories&categories=638')
+ async fetchTournamentNews() {
+ const 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 + '"')
+ throw new Error(
+ 'WordpressRepository::fetchTournamentNews failed with response status "' +
+ response.status +
+ '"'
+ )
}
const dataObjectToArray = JSON.parse(response.data)
- const sortedData = dataObjectToArray.map(item => ({
+ const sortedData = dataObjectToArray.map((item) => ({
content: item.content.rendered,
- category: item.categories
+ category: item.categories,
}))
- return sortedData.filter(article => article.category[1] !== 284)
+ 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')
+ 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 + '"')
+ 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
+ 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')
+ 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 + '"')
+ 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
+ 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')
+ 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 + '"')
+ throw new Error(
+ 'WordpressRepository::fetchNewshub failed with response status "' +
+ response.status +
+ '"'
+ )
}
const items = JSON.parse(response.data)
- const sortedData = items.map(item => ({
+ const sortedData = items.map((item) => ({
category: item.categories,
sortIndex: item.newshub_sortIndex,
link: item.newshub_externalLinkUrl,
@@ -90,10 +122,12 @@ class WordpressRepository {
title: item.title.rendered,
content: item.content.rendered,
author: item._embedded.author[0].name,
- media: item._embedded['wp:featuredmedia'][0].source_url
+ media: item._embedded['wp:featuredmedia'][0].source_url,
}))
- sortedData.sort((articleA, articleB) => articleB.sortIndex - articleA.sortIndex)
+ sortedData.sort(
+ (articleA, articleB) => articleB.sortIndex - articleA.sortIndex
+ )
return sortedData.filter((article) => {
return article.category[1] !== 284
diff --git a/src/backend/services/WordpressService.js b/src/backend/services/WordpressService.js
index 4fd73f6a..881aacb1 100644
--- a/src/backend/services/WordpressService.js
+++ b/src/backend/services/WordpressService.js
@@ -2,7 +2,7 @@ const { MutexService } = require('./MutexService')
const wordpressTTL = 60 * 60
class WordpressService {
- constructor (cacheService, wordpressRepository, lockTimeout = 3000) {
+ constructor(cacheService, wordpressRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.mutexServices = {
@@ -10,16 +10,16 @@ class WordpressService {
tournament: new MutexService(),
creators: new MutexService(),
teams: new MutexService(),
- newshub: new MutexService()
+ newshub: new MutexService(),
}
this.wordpressRepository = wordpressRepository
}
- getCacheKey (name) {
+ getCacheKey(name) {
return 'WordpressService_' + name
}
- async getNews (ignoreCache = false) {
+ async getNews(ignoreCache = false) {
const cacheKey = this.getCacheKey('news')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -27,8 +27,7 @@ class WordpressService {
}
if (this.mutexServices.news.locked) {
- await this.mutexServices.news.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexServices.news.acquire(() => {}, this.lockTimeout)
return this.getNews()
}
@@ -40,7 +39,7 @@ class WordpressService {
return this.getNews()
}
- async getTournamentNews (ignoreCache = false) {
+ async getTournamentNews(ignoreCache = false) {
const cacheKey = this.getCacheKey('tournament-news')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -48,8 +47,7 @@ class WordpressService {
}
if (this.mutexServices.tournament.locked) {
- await this.mutexService.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexService.acquire(() => {}, this.lockTimeout)
return this.getTournamentNews()
}
@@ -61,7 +59,7 @@ class WordpressService {
return this.getTournamentNews()
}
- async getContentCreators (ignoreCache = false) {
+ async getContentCreators(ignoreCache = false) {
const cacheKey = this.getCacheKey('content-creators')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -69,8 +67,10 @@ class WordpressService {
}
if (this.mutexServices.creators.locked) {
- await this.mutexServices.creators.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexServices.creators.acquire(
+ () => {},
+ this.lockTimeout
+ )
return this.getContentCreators()
}
@@ -82,7 +82,7 @@ class WordpressService {
return this.getContentCreators()
}
- async getFafTeams (ignoreCache = false) {
+ async getFafTeams(ignoreCache = false) {
const cacheKey = this.getCacheKey('faf-teams')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -90,8 +90,7 @@ class WordpressService {
}
if (this.mutexServices.teams.locked) {
- await this.mutexService.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexService.acquire(() => {}, this.lockTimeout)
return this.getFafTeams()
}
@@ -103,7 +102,7 @@ class WordpressService {
return this.getFafTeams()
}
- async getNewshub (ignoreCache = false) {
+ async getNewshub(ignoreCache = false) {
const cacheKey = this.getCacheKey('newshub')
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
@@ -111,8 +110,7 @@ class WordpressService {
}
if (this.mutexServices.newshub.locked) {
- await this.mutexService.acquire(() => {
- }, this.lockTimeout)
+ await this.mutexService.acquire(() => {}, this.lockTimeout)
return this.getNewshub()
}
diff --git a/src/backend/services/WordpressServiceFactory.js b/src/backend/services/WordpressServiceFactory.js
index 8352931b..1162c101 100644
--- a/src/backend/services/WordpressServiceFactory.js
+++ b/src/backend/services/WordpressServiceFactory.js
@@ -5,9 +5,12 @@ const cacheService = require('./CacheService')
module.exports = (wordpressBaseURL) => {
const config = {
- baseURL: wordpressBaseURL
+ baseURL: wordpressBaseURL,
}
const wordpressClient = new Axios(config)
- return new WordpressService(cacheService, new WordpressRepository(wordpressClient))
+ return new WordpressService(
+ cacheService,
+ new WordpressRepository(wordpressClient)
+ )
}
diff --git a/src/backend/templates/layouts/default.pug b/src/backend/templates/layouts/default.pug
index e70ed0e9..42fe9d57 100755
--- a/src/backend/templates/layouts/default.pug
+++ b/src/backend/templates/layouts/default.pug
@@ -1,290 +1,298 @@
include ../mixins/flash-connect
doctype html
html(lang='en')
- //- HTML HEADER
- head
- meta(charset="utf-8")
- meta(name="viewport", content="width=device-width, initial-scale=1.0")
- meta(http-equiv="X-UA-Compatible" content="IE=edge")
- meta(name="description" content="FAF Community Website")
+ //- HTML HEADER
+ head
+ meta(charset='utf-8')
+ meta(name='viewport', content='width=device-width, initial-scale=1.0')
+ meta(http-equiv='X-UA-Compatible', content='IE=edge')
+ meta(name='description', content='FAF Community Website')
+
+ title= title || 'Forged Alliance Forever'
+ link(
+ rel='shortcut icon',
+ href='/images/favicon-package/favicon.ico',
+ type='image/png'
+ )
+
+ // Fonts
+ link(
+ href='https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz',
+ rel='stylesheet',
+ type='text/css'
+ )
+ link(
+ href='https://fonts.googleapis.com/css2?family=Electrolize&display=swap',
+ rel='stylesheet'
+ )
+
+ link(
+ href='https://fonts.googleapis.com/css2?family=Electrolize&family=Chakra+Petch&family=Russo+One&display=swap',
+ rel='stylesheet',
+ type='text/css'
+ )
+ link(
+ rel='stylesheet',
+ href='https://fonts.googleapis.com/css2?family=Orbitron&display=swap'
+ )
+
+ //- Customise the stylesheet for your site by editing /public/styles/site.sass
+ link(
+ href='/styles/css/site.min.css?version=' + Date.now(),
+ rel='stylesheet'
+ )
+
+ //- Include template-specific stylesheets by extending the css block
+ block css
+
+ //- Add any other template-specific HEAD tags by extending the head block
+ block head
+
+ //- HTML BODY
+ body
+ .mainTopNavContainer
+ // this is the "thin" navbar that has forums, discord, youtube icons, etc.
+ .topNavContainer
+ a.topnav_item(href='/account/requestPasswordReset') Reset Password
+ .topNavContainer
+ a.topnav_item(href='/donation') Support FAF
+ .topNavContainer
+ a.topnav_item(href='https://forum.faforever.com/') Forums
+ .topNavContainer
+ a.topnav_item(href='https://wiki.faforever.com/en/home') Wiki
+ .topNavContainer
+ a(href='https://discord.gg/mXahVSKGVb')
+ img(src='/images/fontAwesomeIcons/discord.svg')
+ .topNavContainer
+ a(href='https://www.youtube.com/c/ForgedAllianceForever')
+ img(src='/images/fontAwesomeIcons/youtube.svg')
+ .topNavContainer
+ a(href='https://www.twitch.tv/faflive')
+ img(src='/images/fontAwesomeIcons/twitch.svg')
+ .topNavContainer
+ a(href='https://github.com/FAForever')
+ img(src='/images/fontAwesomeIcons/github.svg')
+ .topNavContainer
+ a(href='https://twitter.com/FAFOfficial_')
+ img(src='/images/fontAwesomeIcons/twitter.svg')
+ .topNavContainer
+ a(href='https://www.reddit.com/r/FAF')
+ img(src='/images/fontAwesomeIcons/reddit.svg')
+ // Main Navbar with FAF NEWS, LOGIN AND DOWNLOAD.
+ .mainNavContainer
+ .navContainer
+ .navItem
+ .navLogo
+ a(href='/')
+ img(src='/images/logos/faflogo.svg', alt='')
+ .navContainer
+ ul.navItem
+ a(href='/')
+ li.navList HOME
+ ul.navAbsolute
+ a(href='/news')
+ li.navList NEWS
+ ul.navAbsolute
+ li.navList GAME
+ ul.navAbsolute
+ a(href='/campaign-missions')
+ li Campaign & Co-Op Missions
+ a(href='/tutorials-guides')
+ li Tutorials/Guides
+ a(href='/ai')
+ li AI/Skirmish
+ a(href='/scfa-vs-faf')
+ li SC:FA vs FAF
+
+ li.navList COMMUNITY
+ ul.navAbsolute
+ a(href='/faf-teams')
+ li Teams and Association
+ a(href='/content-creators')
+ li Content Creators
+ a(href='/contribution')
+ li Contribute/Volunteer
+ a(href='/donation')
+ li Donate to FAF
+ a(href='/clans')
+ li Clans
+
+ a(href='/leaderboards')
+ li.navList LEADERBOARDS
+ ul.navAbsolute
+ a(href='/play')
+ li.navList PLAY NOW
+ ul.navAbsolute
+
+ if !appGlobals.loggedInUser
+ .navContainer.navEnd
+ ul.navItem
+ a(href='/login')
+ li.navList LOGIN
+ .navContainer.navEnd
+ ul.navItem
+ a(href='/account/register')
+ li.navList REGISTER
+
+ if appGlobals.loggedInUser
+ #loginList.navContainer.navEnd
+ ul.loginItem
+ .navList
+ img(src='/images/fontAwesomeIcons/user.svg')
+ h3 #{ appGlobals.loggedInUser.name }
+ ul#loginAbsolute
+ li.loginDropdown
+ ul(role='menu')
+ if appGlobals.loggedInUser.clan
+ a(
+ href='/clans/view/' + appGlobals.loggedInUser.clan.id
+ ): li My Clan
+ else
+ a(href='/clans/create'): li Create Clan
+ a(href='/account/changeEmail'): li Change Email
+ a(href='/account/changePassword'): li Change Password
+ a(href='/account/changeUsername'): li Change Username
+ a(href='/account/link'): li Steam Linking
+ a(href='/account/linkGog'): li GOG Linking
+ a(href='/account/report'): li Report Player
+ a(href='/logout'): li Log Out
+
+ // the mobilenav is the mobile navbar, it has two stickies, one of them is the sticky for the mobile menu (so it stays with the user) and the second one is the black backgroud (so when the user opens the mobile menu, everything else darkens.)
+
+ .mobileTransition
+ .mobileTransitionContainer
+ img(src='/images/logos/faflogo.svg', alt='')
+ img#openMenu(src='/images/fontAwesomeIcons/menu.svg')
+
+ #mobileNavBar.gridMainContainer
+ img#closeMenu(src='/images/fontAwesomeIcons/remove.svg')
+ img#returnMenu(src='/images/fontAwesomeIcons/caret.svg')
+ .gridItem.column12
+ .splatForgedBorder
+ .movingBackground1
+ .movingBackground2
+ img(src='/images/faflogo.svg', alt='')
+
+ a.mobileNavElement(href='/') Home
+
+ a.mobileNavElement(href='/news') News
+
+ .mobileNavElement.mobileNavMenu Game
+ .mobileNavMenuContent
+ a(href='/leaderboards') Leaderboards
+ a(href='/tutorials-guides') Tutorials/Guides
+ a(href='/ai') AI/Co-op Play
+ a(href='/scfa-vs-faf') SC:FA vs FAF
+ a(href='/play') Play Now
+
+ .mobileNavElement.mobileNavMenu Community
+ .mobileNavMenuContent
+ a(href='/faf-teams') Teams and Association
+ a(href='/content-creators') Content Creators
+ a(href='/donation') Donate
+ a(href='/contribution') Contribute
+ a(href='/clans') Clans
+ if !appGlobals.loggedInUser
+ a.mobileNavElement(href='/login') Login
+
+ if appGlobals.loggedInUser
+ .mobileNavElement.mobileNavMenu My Account
+ .mobileNavMenuContent
+ if appGlobals.loggedInUser.clan
+ a(
+ href='/clans/view/' + appGlobals.loggedInUser.clan.id
+ ) My Clan
+ else
+ a(href='/clans/create') Create Clan
+ a(href='/account/changeEmail') Change Email
+ a(href='/account/changePassword') Change Password
+ a(href='/account/changeUsername') Change Username
+ a(href='/account/report') Report Player
+ a(href='/logout') Log Out
+
+ .mobileSocialMedia
+ a(href='https://discord.gg/mXahVSKGVb')
+ img(src='/images/fontAwesomeIcons/discord.svg')
+ a(href='https://www.twitch.tv/faflive')
+ img(src='/images/fontAwesomeIcons/twitch.svg')
+ a(href='https://www.youtube.com/c/ForgedAllianceForever')
+ img(src='/images/fontAwesomeIcons/youtube.svg')
+ a(href='https://github.com/FAForever')
+ img(src='/images/fontAwesomeIcons/github.svg')
+ .mobileOtherLinks
+ a(href='https://forum.faforever.com/') Forums
+ a(href='https://wiki.faforever.com/en/home') FAF Wiki
+ .splatForgedBorder.transformationReverse.absoluteBottom
+ .movingBackground1
+ .movingBackground2
- title= title || 'Forged Alliance Forever'
- link(rel="shortcut icon", href="/images/favicon-package/favicon.ico", type="image/png")
-
- // Fonts
- link(href='https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz' rel='stylesheet' type='text/css')
- link(href='https://fonts.googleapis.com/css2?family=Electrolize&display=swap' rel='stylesheet')
-
- link(href='https://fonts.googleapis.com/css2?family=Electrolize&family=Chakra+Petch&family=Russo+One&display=swap' rel='stylesheet' type='text/css')
- link(rel='stylesheet' href='https://fonts.googleapis.com/css2?family=Orbitron&display=swap')
-
- //- Customise the stylesheet for your site by editing /public/styles/site.sass
- link(href="/styles/css/site.min.css?version=" + Date.now(), rel="stylesheet")
-
-
-
- //- Include template-specific stylesheets by extending the css block
- block css
-
-
- //- Add any other template-specific HEAD tags by extending the head block
- block head
-
- //- HTML BODY
- body
- .mainTopNavContainer
- // this is the "thin" navbar that has forums, discord, youtube icons, etc.
- .topNavContainer
- a.topnav_item(href='/account/requestPasswordReset') Reset Password
- .topNavContainer
- a.topnav_item(href='/donation') Support FAF
- .topNavContainer
- a.topnav_item(href='https://forum.faforever.com/') Forums
- .topNavContainer
- a.topnav_item(href='https://wiki.faforever.com/en/home') Wiki
- .topNavContainer
- a(href='https://discord.gg/mXahVSKGVb')
- img(src='/images/fontAwesomeIcons/discord.svg')
- .topNavContainer
- a(href='https://www.youtube.com/c/ForgedAllianceForever')
- img(src='/images/fontAwesomeIcons/youtube.svg')
- .topNavContainer
- a(href='https://www.twitch.tv/faflive')
- img(src='/images/fontAwesomeIcons/twitch.svg')
- .topNavContainer
- a(href='https://github.com/FAForever')
- img(src='/images/fontAwesomeIcons/github.svg')
- .topNavContainer
- a(href='https://twitter.com/FAFOfficial_')
- img(src='/images/fontAwesomeIcons/twitter.svg')
- .topNavContainer
- a(href='https://www.reddit.com/r/FAF')
- img(src='/images/fontAwesomeIcons/reddit.svg')
- // Main Navbar with FAF NEWS, LOGIN AND DOWNLOAD.
- .mainNavContainer
- .navContainer
- .navItem
- .navLogo
- a(href='/')
- img(src='/images/logos/faflogo.svg' alt='')
- .navContainer
- ul.navItem
- a(href='/')
- li.navList HOME
- ul.navAbsolute
- a(href='/news')
- li.navList NEWS
- ul.navAbsolute
- li.navList GAME
-
- ul.navAbsolute
- a(href='/campaign-missions')
- li Campaign & Co-Op Missions
- a(href='/tutorials-guides')
- li Tutorials/Guides
- a(href='/ai')
- li AI/Skirmish
- a(href='/scfa-vs-faf')
- li SC:FA vs FAF
-
- li.navList COMMUNITY
-
- ul.navAbsolute
- a(href='/faf-teams')
- li Teams and Association
- a(href='/content-creators')
- li Content Creators
- a(href='/contribution')
- li Contribute/Volunteer
- a(href='/donation')
- li Donate to FAF
- a(href='/clans')
- li Clans
-
- a(href='/leaderboards')
- li.navList LEADERBOARDS
- ul.navAbsolute
- a(href='/play')
- li.navList PLAY NOW
- ul.navAbsolute
-
- if !appGlobals.loggedInUser
- .navContainer.navEnd
- ul.navItem
- a(href="/login")
- li.navList LOGIN
- .navContainer.navEnd
- ul.navItem
- a(href="/account/register")
- li.navList REGISTER
-
- if appGlobals.loggedInUser
- .navContainer.navEnd#loginList
- ul.loginItem
- .navList
- img(src='/images/fontAwesomeIcons/user.svg')
- h3 #{appGlobals.loggedInUser.name}
- ul#loginAbsolute
- li.loginDropdown
- ul(role='menu')
- if appGlobals.loggedInUser.clan
- a(href="/clans/view/" + appGlobals.loggedInUser.clan.id): li My Clan
- else
- a(href="/clans/create"): li Create Clan
- a(href="/account/changeEmail"): li Change Email
- a(href="/account/changePassword"): li Change Password
- a(href="/account/changeUsername"): li Change Username
- a(href="/account/link"): li Steam Linking
- a(href="/account/linkGog"): li GOG Linking
- a(href="/account/report"): li Report Player
- a(href="/logout"): li Log Out
-
-
-
- // the mobilenav is the mobile navbar, it has two stickies, one of them is the sticky for the mobile menu (so it stays with the user) and the second one is the black backgroud (so when the user opens the mobile menu, everything else darkens.)
-
-
- .mobileTransition
- .mobileTransitionContainer
- img(src='/images/logos/faflogo.svg' alt='')
- img#openMenu(src='/images/fontAwesomeIcons/menu.svg')
-
-
- .gridMainContainer#mobileNavBar
- img#closeMenu(src='/images/fontAwesomeIcons/remove.svg')
- img#returnMenu(src='/images/fontAwesomeIcons/caret.svg')
- .gridItem.column12
.splatForgedBorder
- .movingBackground1
- .movingBackground2
- img(src='/images/faflogo.svg' alt='')
-
-
- a(href='/').mobileNavElement Home
-
-
- a(href='/news').mobileNavElement News
-
-
- .mobileNavElement.mobileNavMenu Game
- .mobileNavMenuContent
- a(href='/leaderboards') Leaderboards
- a(href='/tutorials-guides') Tutorials/Guides
- a(href='/ai') AI/Co-op Play
- a(href='/scfa-vs-faf') SC:FA vs FAF
- a(href='/play') Play Now
-
-
- .mobileNavElement.mobileNavMenu Community
- .mobileNavMenuContent
- a(href='/faf-teams') Teams and Association
- a(href='/content-creators') Content Creators
- a(href='/donation') Donate
- a(href='/contribution') Contribute
- a(href='/clans') Clans
- if !appGlobals.loggedInUser
- a(href='/login').mobileNavElement Login
-
-
- if appGlobals.loggedInUser
- .mobileNavElement.mobileNavMenu My Account
- .mobileNavMenuContent
- if appGlobals.loggedInUser.clan
- a(href="/clans/view/" + appGlobals.loggedInUser.clan.id) My Clan
- else
- a(href="/clans/create") Create Clan
- a(href="/account/changeEmail") Change Email
- a(href="/account/changePassword") Change Password
- a(href="/account/changeUsername") Change Username
- a(href="/account/report") Report Player
- a(href="/logout") Log Out
-
-
- .mobileSocialMedia
- a(href='https://discord.gg/mXahVSKGVb')
- img(src='/images/fontAwesomeIcons/discord.svg')
- a(href='https://www.twitch.tv/faflive')
- img(src='/images/fontAwesomeIcons/twitch.svg')
- a(href='https://www.youtube.com/c/ForgedAllianceForever')
- img(src='/images/fontAwesomeIcons/youtube.svg')
- a(href='https://github.com/FAForever')
- img(src='/images/fontAwesomeIcons/github.svg')
- .mobileOtherLinks
- a(href='https://forum.faforever.com/') Forums
- a(href='https://wiki.faforever.com/en/home') FAF Wiki
- .splatForgedBorder.transformationReverse.absoluteBottom
- .movingBackground1
- .movingBackground2
-
-
-
- .splatForgedBorder
- .movingBackground1
- .movingBackground2
-
-
- block bannerData
-
-
- block banner
- mixin banner()
- .mainBannerContainer(class= bannerImage + 'Banner' style='background-image: url(../images/banner/' + bannerImage + '.webp);')
- .bannerContainer.bannerTitle #{bannerFirstTitle}
- .bannerContainer.bannerTitle #{bannerSecondTitle}
- .bannerContainer.bannerSubtitle #{bannerSubTitle}
-
- block bannerButton
- block bannerMixin
- if bannerImage
- +banner()
+ .movingBackground1
+ .movingBackground2
+
+ block bannerData
+
+ block banner
+ mixin banner
+ .mainBannerContainer(
+ class=bannerImage + 'Banner',
+ style='background-image: url(../images/banner/' + bannerImage + '.webp);'
+ )
+ .bannerContainer.bannerTitle #{ bannerFirstTitle }
+ .bannerContainer.bannerTitle #{ bannerSecondTitle }
+ .bannerContainer.bannerSubtitle #{ bannerSubTitle }
+
+ block bannerButton
+ block bannerMixin
+ if bannerImage
+ +banner
+ .splatForgedBorder.transformationReverse
+ .movingBackground1
+ .movingBackground2
+
+ +flash-connect(connectFlash)
+ //- The content block should contain the body of your template's content
+ block content
.splatForgedBorder.transformationReverse
- .movingBackground1
- .movingBackground2
-
- +flash-connect(connectFlash)
- //- The content block should contain the body of your template's content
- block content
- .splatForgedBorder.transformationReverse
- .movingBackground1
- .movingBackground2
-
- .mainFooterContainer
- .footerContainer.column12
- .footerItem
- //.backToTop Back to Top
- .footerContainer.column12
- .footerItem
- a(href='/')
- img(src='/images/logos/faflogo.svg' alt='FAF Logo')
- h1 FORGED ALLIANCE FOREVER
- .footerContainer.column12
- .footerItem
- ul ETIQUETTE
- li
- a(href='/cg') CONTRIBUTION GUIDELINES
- li
- a(href='/rules') RULES
-
- ul CONTRIBUTE
- li
- a(href='/contribution') CONTRIBUTIONS
- li
- a(href='/donation') DONATIONS
-
- ul LEGAL
- li
- a(href='/privacy') PRIVACY STATEMENT
- li
- a(href='/tos') TERMS OF SERVICE
-
- ul CONTACT US
- li
- a(href='https://discord.gg/mXahVSKGVb') DISCORD
- li
- a(href='https://forum.faforever.com/') FORUMS
-
- script(src=webpackAssetJS('navigation'))
-
- //- Include template-specific javascript files by extending the js block
- block js
-
+ .movingBackground1
+ .movingBackground2
+
+ .mainFooterContainer
+ .footerContainer.column12
+ .footerItem
+ //.backToTop Back to Top
+ .footerContainer.column12
+ .footerItem
+ a(href='/')
+ img(src='/images/logos/faflogo.svg', alt='FAF Logo')
+ h1 FORGED ALLIANCE FOREVER
+ .footerContainer.column12
+ .footerItem
+ ul ETIQUETTE
+ li
+ a(href='/cg') CONTRIBUTION GUIDELINES
+ li
+ a(href='/rules') RULES
+
+ ul CONTRIBUTE
+ li
+ a(href='/contribution') CONTRIBUTIONS
+ li
+ a(href='/donation') DONATIONS
+
+ ul LEGAL
+ li
+ a(href='/privacy') PRIVACY STATEMENT
+ li
+ a(href='/tos') TERMS OF SERVICE
+
+ ul CONTACT US
+ li
+ a(href='https://discord.gg/mXahVSKGVb') DISCORD
+ li
+ a(href='https://forum.faforever.com/') FORUMS
+
+ script(src=webpackAssetJS('navigation'))
+
+ //- Include template-specific javascript files by extending the js block
+ block js
diff --git a/src/backend/templates/mixins/flash-connect.pug b/src/backend/templates/mixins/flash-connect.pug
index 569b33b8..c57ac3e7 100644
--- a/src/backend/templates/mixins/flash-connect.pug
+++ b/src/backend/templates/mixins/flash-connect.pug
@@ -2,12 +2,12 @@ mixin flash-connect(connectFlash)
- var flashes = connectFlash()
if flashes
if flashes.info
- div.alert(class='alert-success')
+ .alert.alert-success
ul
each info in flashes.info
- li !{info}
+ li !{ info }
if flashes.error
- div.alert(class='alert-danger')
+ .alert.alert-danger
ul
each error in flashes.error
- li !{error}
+ li !{ error }
diff --git a/src/backend/templates/mixins/flash-error.pug b/src/backend/templates/mixins/flash-error.pug
index bf98fcf5..20541fc8 100644
--- a/src/backend/templates/mixins/flash-error.pug
+++ b/src/backend/templates/mixins/flash-error.pug
@@ -1,7 +1,7 @@
mixin flash-error(validationErrors)
if validationErrors && validationErrors.messages
- div.alert(class=validationErrors.class)
- ul.validationErrors-errors
- each error in validationErrors.messages.errors
- if error.msg
- li #{validationErrors.type} !{error.msg}
+ .alert(class=validationErrors.class)
+ ul.validationErrors-errors
+ each error in validationErrors.messages.errors
+ if error.msg
+ li #{ validationErrors.type } !{ error.msg }
diff --git a/src/backend/templates/mixins/flash-messages.pug b/src/backend/templates/mixins/flash-messages.pug
index 43827b47..831faf55 100644
--- a/src/backend/templates/mixins/flash-messages.pug
+++ b/src/backend/templates/mixins/flash-messages.pug
@@ -1,8 +1,8 @@
mixin flash-messages(messages)
- if flash
- div.alert(class=flash['class'])
- ul.flash-errors
- if flash.messages
- if flash.messages[0]
- if flash.messages[0].msg
- li #{flash.type} !{flash.messages[0].msg}
+ if flash
+ .alert(class=flash['class'])
+ ul.flash-errors
+ if flash.messages
+ if flash.messages[0]
+ if flash.messages[0].msg
+ li #{ flash.type } !{ flash.messages[0].msg }
diff --git a/src/backend/templates/mixins/form/account.pug b/src/backend/templates/mixins/form/account.pug
index d31a65d0..352255f9 100644
--- a/src/backend/templates/mixins/form/account.pug
+++ b/src/backend/templates/mixins/form/account.pug
@@ -1,79 +1,126 @@
mixin email
- p Enter your Email
- .formStart
- input(type='email', name='email', required='required', placeholder='UEF@email.com', value=formData['email'])
- span(aria-hidden='true')
-
-
+ p Enter your Email
+ .formStart
+ input(
+ type='email',
+ name='email',
+ required='required',
+ placeholder='UEF@email.com',
+ value=formData['email']
+ )
+ span(aria-hidden='true')
mixin username
p Enter your username
.formStart
- //value=formData['username']
- input(type='text', name='username', required='required', data-minlength='3', maxlength='16', pattern='^[A-Za-z]{1}[A-Za-z0-9_\\-]{2,15}$', data-minlength-error='The username is too short - must be at least 3 characters', data-error='Please check username requirements', data-remote="/account/checkUsername", data-remote-error="Username taken", placeholder='Eco_Player7-', value=formData['username'])
- span(aria-hidden='true')
- .formHelp
- ul Your username must:
- li Start with a letter
-
- li Be between 3 and 16 characters
-
- li Only use letters and digits ( - and _ are allowed).
+ //value=formData['username']
+ input(
+ type='text',
+ name='username',
+ required='required',
+ data-minlength='3',
+ maxlength='16',
+ pattern='^[A-Za-z]{1}[A-Za-z0-9_\\-]{2,15}$',
+ data-minlength-error='The username is too short - must be at least 3 characters',
+ data-error='Please check username requirements',
+ data-remote='/account/checkUsername',
+ data-remote-error='Username taken',
+ placeholder='Eco_Player7-',
+ value=formData['username']
+ )
+ span(aria-hidden='true')
+ .formHelp
+ ul Your username must:
+ li Start with a letter
+ li Be between 3 and 16 characters
+ li Only use letters and digits ( - and _ are allowed).
mixin usernameOrEmail
- .form-group
- label Username or email:
- .input-group
- input(type='text', name='usernameOrEmail', required='required', value=formData['usernameOrEmail']).form-control
- span(aria-hidden='true')
+ .form-group
+ label Username or email:
+ .input-group
+ input.form-control(
+ type='text',
+ name='usernameOrEmail',
+ required='required',
+ value=formData['usernameOrEmail']
+ )
+ span(aria-hidden='true')
mixin confirm-password(passwordName,labelPassword)
- - passwordName = passwordName || 'password'
- - labelPassword = labelPassword || ''
- .form-group.has-feedback
- label #{labelPassword} Password:
- .input-group
- input(type='password', name=passwordName, required='required', id='inputPassword', data-minlength='6').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
- .help-block Minimum of 6 characters
- .form-group.has-feedback
- label Confirm #{labelPassword} Password:
- .input-group
- input(type='password', name=passwordName + '_confirm', required='required', data-match='#inputPassword', data-match-error="Passwords don't match. Please fix!", data-minlength='6').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
- .help-block.with-errors
+ - passwordName = passwordName || 'password'
+ - labelPassword = labelPassword || ''
+ .form-group.has-feedback
+ label #{ labelPassword } Password:
+ .input-group
+ input#inputPassword.form-control(
+ type='password',
+ name=passwordName,
+ required='required',
+ data-minlength='6'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
+ .help-block Minimum of 6 characters
+ .form-group.has-feedback
+ label Confirm #{ labelPassword } Password:
+ .input-group
+ input.form-control(
+ type='password',
+ name=passwordName + '_confirm',
+ required='required',
+ data-match='#inputPassword',
+ data-match-error='Passwords don\'t match. Please fix!',
+ data-minlength='6'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
+ .help-block.with-errors
mixin oldPassword(labelPassword)
- - labelPassword = labelPassword || ''
- .form-group.has-feedback
- label #{labelPassword} Password:
- .input-group
- input(type='password', name='old_password', required='required', id='oldPassword').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
+ - labelPassword = labelPassword || ''
+ .form-group.has-feedback
+ label #{ labelPassword } Password:
+ .input-group
+ input#oldPassword.form-control(
+ type='password',
+ name='old_password',
+ required='required'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
mixin currentPassword(labelPassword)
- - labelPassword = labelPassword || ''
- .form-group
- label #{labelPassword} Password:
- .input-group
- input(type='password', name='password', required='required', id='password').form-control
+ - labelPassword = labelPassword || ''
+ .form-group
+ label #{ labelPassword } Password:
+ .input-group
+ input#password.form-control(
+ type='password',
+ name='password',
+ required='required'
+ )
mixin tosagree(checked)
- - checked = checked || false
- .tosAgree
- label Agree to our Terms of Service
- input(type='checkbox', name='tosagree', required='required')
- label Agree to our Privacy Statement
- input(type='checkbox', name='privacyagree', required='required')
- //label Agree to our Contribution Guidelines
- input(type='checkbox', name='gcagree', required='required')
+ - checked = checked || false
+ .tosAgree
+ label Agree to our Terms of Service
+ input(type='checkbox', name='tosagree', required='required')
+ label Agree to our Privacy Statement
+ input(type='checkbox', name='privacyagree', required='required')
+ //label Agree to our Contribution Guidelines
+ input(type='checkbox', name='gcagree', required='required')
mixin gogUsername
- .form-group.has-feedback
- label GOG account name:
- .input-group
- input(type='text', name='gog_username', id='gogUsername', required='required', data-minlength='3', maxlength='100', pattern='[A-Za-z0-9._-]{2,99}$').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
- .help-block.with-errors
+ .form-group.has-feedback
+ label GOG account name:
+ .input-group
+ input#gogUsername.form-control(
+ type='text',
+ name='gog_username',
+ required='required',
+ data-minlength='3',
+ maxlength='100',
+ pattern='[A-Za-z0-9._-]{2,99}$'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
+ .help-block.with-errors
diff --git a/src/backend/templates/views/account/activate.pug b/src/backend/templates/views/account/activate.pug
index e67475fa..8c521e04 100644
--- a/src/backend/templates/views/account/activate.pug
+++ b/src/backend/templates/views/account/activate.pug
@@ -4,33 +4,53 @@ include ../../mixins/form/account
block bannerMixin
block content
- .activationContainer
- br
- br
- h1 Welcome #{username ? username : ''} to FAF!
- h3 Just set your new account's password below and your registration will be complete.
-
- +flash-messages(flash)
- .column12
- form(method='post',action="/account/activate?username="+username+"&token="+token,data-toggle="validator")
- - passwordName = passwordName || 'password'
- - labelPassword = labelPassword || ''
- .form-group.has-feedback
- label #{labelPassword} Password:
- .input-group
- input(type='password', name=passwordName, required='required', id='inputPassword', data-minlength='6').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
- .help-block Minimum of 6 characters
+ .activationContainer
+ br
br
- .form-group.has-feedback
- label Confirm #{labelPassword} Password:
- .input-group
- input(type='password', name=passwordName + '_confirm', required='required',data-match='#inputPassword', data-match-error="Passwords don't match. Please fix!", data-minlength='6').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
- .help-block.with-errors
- .form-actions
- br
- button(type='submit') Register
- br
- br
- br
+ h1 Welcome #{ username ? username : '' } to FAF!
+ h3 Just set your new account's password below and your registration will be complete.
+
+ +flash-messages(flash)
+ .column12
+ form(
+ method='post',
+ action='/account/activate?username=' + username + '&token=' + token,
+ data-toggle='validator'
+ )
+ - passwordName = passwordName || 'password'
+ - labelPassword = labelPassword || ''
+ .form-group.has-feedback
+ label #{ labelPassword } Password:
+ .input-group
+ input#inputPassword.form-control(
+ type='password',
+ name=passwordName,
+ required='required',
+ data-minlength='6'
+ )
+ span.glyphicon.form-control-feedback(
+ aria-hidden='true'
+ )
+ .help-block Minimum of 6 characters
+ br
+ .form-group.has-feedback
+ label Confirm #{ labelPassword } Password:
+ .input-group
+ input.form-control(
+ type='password',
+ name=passwordName + '_confirm',
+ required='required',
+ data-match='#inputPassword',
+ data-match-error='Passwords don\'t match. Please fix!',
+ data-minlength='6'
+ )
+ span.glyphicon.form-control-feedback(
+ aria-hidden='true'
+ )
+ .help-block.with-errors
+ .form-actions
+ br
+ button(type='submit') Register
+ br
+ br
+ br
diff --git a/src/backend/templates/views/account/changeEmail.pug b/src/backend/templates/views/account/changeEmail.pug
index 2a491f6f..b18b0551 100644
--- a/src/backend/templates/views/account/changeEmail.pug
+++ b/src/backend/templates/views/account/changeEmail.pug
@@ -15,8 +15,14 @@ block content
.row
.col-md-offset-3.col-md-6
- form(method='post',action="/account/changeEmail",data-toggle="validator")
+ form(
+ method='post',
+ action='/account/changeEmail',
+ data-toggle='validator'
+ )
+email
+currentPassword
.form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Change Email
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Change Email
diff --git a/src/backend/templates/views/account/changePassword.pug b/src/backend/templates/views/account/changePassword.pug
index c5a08598..019caae5 100644
--- a/src/backend/templates/views/account/changePassword.pug
+++ b/src/backend/templates/views/account/changePassword.pug
@@ -15,10 +15,16 @@ block content
.row
.col-md-offset-3.col-md-6
- form(method='post',action="/account/changePassword",data-toggle="validator")
+ form(
+ method='post',
+ action='/account/changePassword',
+ data-toggle='validator'
+ )
+oldPassword('Old')
+confirm-password('','New')
.form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Change
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Change
block js
diff --git a/src/backend/templates/views/account/changeUsername.pug b/src/backend/templates/views/account/changeUsername.pug
index cc3cb3a5..79ccca55 100644
--- a/src/backend/templates/views/account/changeUsername.pug
+++ b/src/backend/templates/views/account/changeUsername.pug
@@ -3,17 +3,23 @@ include ../../mixins/flash-messages
include ../../mixins/form/account
block bannerMixin
block content
- .containerCenter
- h2.account-title Change Username
- h4 Change your username by using the form below. Once it is changed, you can log in to your account with the new username.
You can only change the name every 30 days and your old username will be reserved for 6 months.
In case you choose an offending username you might get banned.
- br
- .row
- .col-md-offset-3.col-md-6
- +flash-messages(flash)
+ .containerCenter
+ h2.account-title Change Username
+ h4 Change your username by using the form below. Once it is changed, you can log in to your account with the new username.
You can only change the name every 30 days and your old username will be reserved for 6 months.
In case you choose an offending username you might get banned.
+ br
+ .row
+ .col-md-offset-3.col-md-6
+ +flash-messages(flash)
- .row
- .col-md-offset-3.col-md-6
- form(method='post',action="/account/changeUsername",data-toggle="validator")
- +username
- .form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Change
+ .row
+ .col-md-offset-3.col-md-6
+ form(
+ method='post',
+ action='/account/changeUsername',
+ data-toggle='validator'
+ )
+ +username
+ .form-actions
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Change
diff --git a/src/backend/templates/views/account/confirmPasswordReset.pug b/src/backend/templates/views/account/confirmPasswordReset.pug
index 0c9d8b34..dbec183d 100644
--- a/src/backend/templates/views/account/confirmPasswordReset.pug
+++ b/src/backend/templates/views/account/confirmPasswordReset.pug
@@ -7,14 +7,20 @@ block content
.row
.col-md-12
h1.account-title Reset Password
- h4.account-subtitle.text-center Hello #{username}, please enter your new password!
+ h4.account-subtitle.text-center Hello #{ username }, please enter your new password!
.row
.col-md-offset-3.col-md-6
+flash-messages(flash)
.row
.col-md-offset-3.col-md-6
- form(method='post', action="/account/password/confirmReset?username="+username+"&token="+token, data-toggle="validator")
+ form(
+ method='post',
+ action='/account/password/confirmReset?username=' + username + '&token=' + token,
+ data-toggle='validator'
+ )
+confirm-password
.form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Reset
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Reset
diff --git a/src/backend/templates/views/account/confirmResyncAccount.pug b/src/backend/templates/views/account/confirmResyncAccount.pug
index 99fc6f3a..ef91176e 100644
--- a/src/backend/templates/views/account/confirmResyncAccount.pug
+++ b/src/backend/templates/views/account/confirmResyncAccount.pug
@@ -11,8 +11,8 @@ block content
.row
.col-md-12
p In rare cases your FAF account goes out of sync with other services (e.g. the forum). This can lead to outdated data (e.g. username or email ) in these services. Resyncronisation sends your current account data to these services again.
- p There is no point in executing this function unless our technical support has requested it.
-
+ p There is no point in executing this function unless our technical support has requested it.
+
.row
.col-md-offset-3.col-md-6
+flash-messages(flash)
diff --git a/src/backend/templates/views/account/createAccount.pug b/src/backend/templates/views/account/createAccount.pug
index a8c0f671..d9560209 100644
--- a/src/backend/templates/views/account/createAccount.pug
+++ b/src/backend/templates/views/account/createAccount.pug
@@ -15,4 +15,3 @@ block content
.col-md-offset-3.col-md-6
a(href=tokenURL)
button.btn.btn-default.btn-lg.btn-outro.btn-danger Create account
-
diff --git a/src/backend/templates/views/account/linkGog.pug b/src/backend/templates/views/account/linkGog.pug
index 708c39e2..c184590c 100644
--- a/src/backend/templates/views/account/linkGog.pug
+++ b/src/backend/templates/views/account/linkGog.pug
@@ -3,67 +3,67 @@ include ../../mixins/flash-messages
include ../../mixins/form/account
block bannerMixin
block content
- .playMain
- .playInnerGrid
- .playContainer
- h2 How to link FAF account to GOG?
- p Shouldnt take more than 1-2 minutes.
+ .playMain
+ .playInnerGrid
+ .playContainer
+ h2 How to link FAF account to GOG?
+ p Shouldnt take more than 1-2 minutes.
- .playCheckboxContainer
- input(type='checkbox')
- label Go to your Profile inside GOG -> Order & Settings -> Privacy -> Set everything to Everyone/All visitors (once the linking is done, you can revert this)
- br
- .gogSmallerImage
- img(src='/images/steamGOG/gog1.jpg')
- img(src='/images/steamGOG/gog2.jpg')
- br
- br
- img(src='/images/steamGOG/gog3.jpg')
+ .playCheckboxContainer
+ input(type='checkbox')
+ label Go to your Profile inside GOG -> Order & Settings -> Privacy -> Set everything to Everyone/All visitors (once the linking is done, you can revert this)
+ br
+ .gogSmallerImage
+ img(src='/images/steamGOG/gog1.jpg')
+ img(src='/images/steamGOG/gog2.jpg')
+ br
+ br
+ img(src='/images/steamGOG/gog3.jpg')
-
+ br
+ input(type='checkbox')
+ label Switch your profile's about you to the code below (so we can make sure you own SC:FA).
+ br
+ h2 #{ gogToken }
+ br
+ img(src='/images/steamGOG/gog4.jpg')
+ .gogSmallerImage2
+ br
+ img(src='/images/steamGOG/gog5.jpg')
+ br
+ br
+ img(src='/images/steamGOG/gog6.jpg')
+ br
+ br
+ input(type='checkbox')
+ label Write your GOG username below and then click the link accounts button.
+ br
+ +flash-messages(flash)
- br
- input(type='checkbox')
- label Switch your profile's about you to the code below (so we can make sure you own SC:FA).
- br
- h2 #{gogToken}
- br
- img(src='/images/steamGOG/gog4.jpg')
- .gogSmallerImage2
-
-
- br
- img(src='/images/steamGOG/gog5.jpg')
- br
- br
- img(src='/images/steamGOG/gog6.jpg')
+ br
+ .displayBlock
+ form(
+ method='post',
+ action='/account/linkGog',
+ data-toggle='validator'
+ )
+ +gogUsername
+ .form-actions
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Link accounts
+ br
+ input(type='checkbox')
+ label Make sure the flash message below states you are connected.
+ br
+ p Flash Message:
+ +flash-messages(flash)
+ br
+ h1 And you are done with GOG linking forever!
+ p Now if you want, you can set your privacy settings back to private and your profile's about you to normal.
- br
- br
- input(type='checkbox')
- label Write your GOG username below and then click the link accounts button.
- br
- +flash-messages(flash)
-
- br
- .displayBlock
- form(method='post', action="/account/linkGog", data-toggle="validator")
- +gogUsername
- .form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Link accounts
- br
- input(type='checkbox')
- label Make sure the flash message below states you are connected.
-
- br
- p Flash Message:
- +flash-messages(flash)
- br
- h1 And you are done with GOG linking forever!
- p Now if you want, you can set your privacy settings back to private and your profile's about you to normal.
-
- h2 Why do I need to link my Steam/GOG account to FAF?
- p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
- p Linking your GOG account to FAF doesn't provide us with any information or powers over your account.
Only the fact that you own Supreme Commander: Forged Alliance.
Which is why we need you to set your Game Details to public when linking your account.
+ h2 Why do I need to link my Steam/GOG account to FAF?
+ p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
+ p Linking your GOG account to FAF doesn't provide us with any information or powers over your account.
Only the fact that you own Supreme Commander: Forged Alliance.
Which is why we need you to set your Game Details to public when linking your account.
diff --git a/src/backend/templates/views/account/linkSteam.pug b/src/backend/templates/views/account/linkSteam.pug
index ca9da438..2b4f7b86 100644
--- a/src/backend/templates/views/account/linkSteam.pug
+++ b/src/backend/templates/views/account/linkSteam.pug
@@ -3,49 +3,45 @@ include ../../mixins/flash-messages
include ../../mixins/form/account
block bannerMixin
block content
-
-
- .playMain
- .playInnerGrid
- .playContainer
- h2 How to link FAF account to Steam?
- p Shouldnt take more than 1-2 minutes.
-
- .playCheckboxContainer
- input(type='checkbox')
- label Go to your Profile inside Steam -> Edit Profile -> My Privacy Settings -> Set Game details to "Public"
- br
- img(src='/images/steamGOG/steamPublic.jpg')
- br
- br
- img(src='/images/steamGOG/steamEditProfile.jpg')
- br
- br
- img(src='/images/steamGOG/steamSettings.jpg')
-
-
- br
- input(type='checkbox')
- label Login to Steam and link your account below
- br
- a(href=steamConnect target='_blank')
- button Link your FAF account to Steam
- br
- img(src='/images/steamGOG/steamLogin.jpg')
-
-
- br
-
- input(type='checkbox')
- label Make sure the flash message below states you are connected.
-
- br
- p Flash Message:
- +flash-messages(flash)
- br
- h1 And you are done with Steam link forever!
- p Now if you want, you can set your Game Details back to private.
-
- h2 Why do I need to link my Steam/GOG account to FAF?
- p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
- p Linking your steam account to FAF doesn't provide us with any information or powers over your account.
Only the fact that you own Supreme Commander: Forged Alliance.
Which is why we need you to set your Game Details to public when linking your account.
+ .playMain
+ .playInnerGrid
+ .playContainer
+ h2 How to link FAF account to Steam?
+ p Shouldnt take more than 1-2 minutes.
+
+ .playCheckboxContainer
+ input(type='checkbox')
+ label Go to your Profile inside Steam -> Edit Profile -> My Privacy Settings -> Set Game details to "Public"
+ br
+ img(src='/images/steamGOG/steamPublic.jpg')
+ br
+ br
+ img(src='/images/steamGOG/steamEditProfile.jpg')
+ br
+ br
+ img(src='/images/steamGOG/steamSettings.jpg')
+
+ br
+ input(type='checkbox')
+ label Login to Steam and link your account below
+ br
+ a(href=steamConnect, target='_blank')
+ button Link your FAF account to Steam
+ br
+ img(src='/images/steamGOG/steamLogin.jpg')
+
+ br
+
+ input(type='checkbox')
+ label Make sure the flash message below states you are connected.
+
+ br
+ p Flash Message:
+ +flash-messages(flash)
+ br
+ h1 And you are done with Steam link forever!
+ p Now if you want, you can set your Game Details back to private.
+
+ h2 Why do I need to link my Steam/GOG account to FAF?
+ p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
+ p Linking your steam account to FAF doesn't provide us with any information or powers over your account.
Only the fact that you own Supreme Commander: Forged Alliance.
Which is why we need you to set your Game Details to public when linking your account.
diff --git a/src/backend/templates/views/account/register.pug b/src/backend/templates/views/account/register.pug
index c7382527..f4f08656 100644
--- a/src/backend/templates/views/account/register.pug
+++ b/src/backend/templates/views/account/register.pug
@@ -3,34 +3,39 @@ include ../../mixins/form/account
include ../../mixins/flash-messages
block bannerMixin
-
-block content
- .containerCenter
- .accountForm
- .column12
- h1 Get Started
- h2 Welcome to the FAF community
- br
- +flash-messages(flash)
-
- form(method='post', action="/account/register", data-toggle="validator")
- +username
- br
- br
- br
-
- +email
- br
- br
- +tosagree
- .recaptchaForm
- label.column12
- .g-recaptcha(data-sitekey=recaptchaSiteKey)
- p We will send you an email with a link. The link will lead you to a page where you can set your password and activate your account.
- p If you don't receive an email please check your spam folder!
- .form-actions
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Register
+block content
+ .containerCenter
+ .accountForm
+ .column12
+ h1 Get Started
+ h2 Welcome to the FAF community
+ br
+ +flash-messages(flash)
+
+ form(
+ method='post',
+ action='/account/register',
+ data-toggle='validator'
+ )
+ +username
+ br
+ br
+ br
+
+ +email
+ br
+ br
+ +tosagree
+ .recaptchaForm
+ label.column12
+ .g-recaptcha(data-sitekey=recaptchaSiteKey)
+ p We will send you an email with a link. The link will lead you to a page where you can set your password and activate your account.
+ p If you don't receive an email please check your spam folder!
+ .form-actions
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Register
block js
- script(src='//www.google.com/recaptcha/api.js')
+ script(src='//www.google.com/recaptcha/api.js')
diff --git a/src/backend/templates/views/account/report.pug b/src/backend/templates/views/account/report.pug
index b94814db..b9a568c8 100755
--- a/src/backend/templates/views/account/report.pug
+++ b/src/backend/templates/views/account/report.pug
@@ -2,14 +2,17 @@ extends ../../layouts/default
include ../../mixins/flash-error
include ../../mixins/form/account
block css
- link(href="/styles/awesomplete.css?version=" + Date.now(), rel="stylesheet")
+ link(
+ href='/styles/awesomplete.css?version=' + Date.now(),
+ rel='stylesheet'
+ )
block bannerMixin
block content
.containerCenter.text-center
.row
.col-md-12
h1.account-title Report a FAF user
- div(style={'text-align':'left'})
+ div(style={ 'text-align': 'left' })
p Here you can report players who have broken the community rules in some way. We encourage users to report misconducting players to keep Forged Alliance Forever a healthy community. All reports will be processed by our moderation team.
p Examples of reportable behaviour:
ul
@@ -21,7 +24,9 @@ block content
li Exploits
p Bugs in the game should be
- a(href='https://forum.faforever.com/category/8/game-issues-and-gameplay-questions') submitted to our tech support forum
+ a(
+ href='https://forum.faforever.com/category/8/game-issues-and-gameplay-questions'
+ ) submitted to our tech support forum
| or preferably as an
a(href='https://github.com/FAForever/fa') issue on our github page.
hr
@@ -31,85 +36,104 @@ block content
.col-md-offset-3.col-md-6
+flash-error(flash)
- form(method='post', action="/account/report", data-toggle="validator").accountForm
+ form.accountForm(
+ method='post',
+ action='/account/report',
+ data-toggle='validator'
+ )
.column6
- div.form-group
+ .form-group
p Reporter:
- input(type='text', disabled='disabled', value=appGlobals.loggedInUser.name).form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
-
- div.form-group
+ input.form-control(
+ type='text',
+ disabled='disabled',
+ value=appGlobals.loggedInUser.name
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
+
+ .form-group
p Offender username:
- input(type='text', required='required', class='offender_name', id='offender', name='offender', placeholder='FAF username(s)').form-control
-
- span(aria-hidden='true').glyphicon.form-control-feedback
+ input#offender.offender_name.form-control(
+ type='text',
+ required='required',
+ name='offender',
+ placeholder='FAF username(s)'
+ )
+
+ span.glyphicon.form-control-feedback(aria-hidden='true')
.help-block Make sure to spell the name correctly and to respect the casing.
-
-
.column6
- div.form-group
+ .form-group
p Replay/Game ID:
- input(type='text', name='game_id', value=game_id, placeholder='(If it happened ingame)').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
+ input.form-control(
+ type='text',
+ name='game_id',
+ value=game_id,
+ placeholder='(If it happened ingame)'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
.help-block
p Please enter the replay ID of the game where the incident happened
-
- div.form-group
+ .form-group
p Timestamp:
- input(type='text', name='game_timecode', placeholder='(If it happened ingame)').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
+ input.form-control(
+ type='text',
+ name='game_timecode',
+ placeholder='(If it happened ingame)'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
.help-block
p Enter in-game time when the incident started here.
.column12
- div.form-group
- p Incident report:
- textarea(rows='8', name='report_description', required='required', placeholder='Please provide a short but thorough description of the incident you are reporting. If there are no records available of the incident (e.g. not something that happened in #aeolus or in-game), please provide us a screenshot of it. You can use any image hosting site, e.g. http://imgur.com/.').form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
+ .form-group
+ p Incident report:
+ textarea.form-control(
+ rows='8',
+ name='report_description',
+ required='required',
+ placeholder='Please provide a short but thorough description of the incident you are reporting. If there are no records available of the incident (e.g. not something that happened in #aeolus or in-game), please provide us a screenshot of it. You can use any image hosting site, e.g. http://imgur.com/.'
+ )
+ span.glyphicon.form-control-feedback(aria-hidden='true')
br
.column12
.form-actions
button(type='submit') Submit Report
h3.column12
- br
- br
- p Current reports
-
-
- .centerFormPlease
- table
- thead
- tr
- th #
- th(style={'text-align':'center'}) Created at
- th(style={'text-align':'center'}) Offender
- th(style={'text-align':'center'}) Game
- th(style={'text-align':'center'}) Description
- th(style={'text-align':'center'}) Moderator
- th(style={'text-align':'center'}) Notice
- th(style={'text-align':'center'}) Status
- tbody
- each report in reports
- tr
- td #{report.id}
- td #{report.creationTime}
- td #{report.offenders}
- td #{report.game}
- td #{report.description}
- td #{report.lastModerator}
- td #{report.notice}
- td(style=report.statusStyle) #{report.status}
-
-
-
-
+ br
+ br
+ p Current reports
+
+ .centerFormPlease
+ table
+ thead
+ tr
+ th #
+ th(style={ 'text-align': 'center' }) Created at
+ th(style={ 'text-align': 'center' }) Offender
+ th(style={ 'text-align': 'center' }) Game
+ th(style={ 'text-align': 'center' }) Description
+ th(style={ 'text-align': 'center' }) Moderator
+ th(style={ 'text-align': 'center' }) Notice
+ th(style={ 'text-align': 'center' }) Status
+ tbody
+ each report in reports
+ tr
+ td #{ report.id }
+ td #{ report.creationTime }
+ td #{ report.offenders }
+ td #{ report.game }
+ td #{ report.description }
+ td #{ report.lastModerator }
+ td #{ report.notice }
+ td(style=report.statusStyle) #{ report.status }
block js
- script(type='text/javascript').
- if (window.history.replaceState) {
- window.history.replaceState(null, null, window.location.href);
- }
+ script(type='text/javascript').
+ if (window.history.replaceState) {
+ window.history.replaceState(null, null, window.location.href)
+ }
- script(src=webpackAssetJS('report'))
+ script(src=webpackAssetJS('report'))
diff --git a/src/backend/templates/views/account/requestPasswordReset.pug b/src/backend/templates/views/account/requestPasswordReset.pug
index 8e6f7e16..8a32dbbb 100644
--- a/src/backend/templates/views/account/requestPasswordReset.pug
+++ b/src/backend/templates/views/account/requestPasswordReset.pug
@@ -3,45 +3,57 @@ include ../../mixins/flash-error
include ../../mixins/form/account
block bannerMixin
block bannerData
- - var bannerFirstTitle = "Reset Password"
- - var bannerSubTitle = ""
+ - var bannerFirstTitle = 'Reset Password'
+ - var bannerSubTitle = ''
block content
- .passResetContainer
- .flashMessage.column12
- +flash-error(errors)
- .passResetEmail.column12
- h1 Reset password via email
- p Enter your username or email below to reset your password.
- br
- form(method='post',action="/account/requestPasswordReset",data-toggle="validator")
- label Username or email:
- .input-group
- input(type='text', name='usernameOrEmail', required='required', value=formData['usernameOrEmail']).form-control
- span(aria-hidden='true').glyphicon.form-control-feedback
+ .passResetContainer
+ .flashMessage.column12
+ +flash-error(errors)
+ .passResetEmail.column12
+ h1 Reset password via email
+ p Enter your username or email below to reset your password.
+ br
+ form(
+ method='post',
+ action='/account/requestPasswordReset',
+ data-toggle='validator'
+ )
+ label Username or email:
+ .input-group
+ input.form-control(
+ type='text',
+ name='usernameOrEmail',
+ required='required',
+ value=formData['usernameOrEmail']
+ )
+ span.glyphicon.form-control-feedback(
+ aria-hidden='true'
+ )
+ br
+ p We will send you an email to your accounts email address containing a link. The link will lead you to a page where you can set your new password.
+ p If you don't receive an email please check your spam folder!
+ br
+ .recaptchaForm
+ label.column12
+ .g-recaptcha(data-sitekey=recaptchaSiteKey)
+ .form-actions
+ button.btn.btn-default.btn-lg.btn-outro.btn-danger(
+ type='submit'
+ ) Reset via email
+ br
+ br
+
br
- p We will send you an email to your accounts email address containing a link. The link will lead you to a page where you can set your new password.
- p If you don't receive an email please check your spam folder!
br
- .recaptchaForm
- label.column12
- .g-recaptcha(data-sitekey=recaptchaSiteKey)
- .form-actions
-
- button(type='submit').btn.btn-default.btn-lg.btn-outro.btn-danger Reset via email
- br
- br
-
- br
- br
- .passResetSteam.column12
- h1 Reset password via Steam
- p Click on the button below to get to the Steam login page.
- a(href=steamReset)
- button Reset via Steam
- br
- br
+ .passResetSteam.column12
+ h1 Reset password via Steam
+ p Click on the button below to get to the Steam login page.
+ a(href=steamReset)
+ button Reset via Steam
+ br
+ br
block js
script(src='//www.google.com/recaptcha/api.js')
diff --git a/src/backend/templates/views/ai.pug b/src/backend/templates/views/ai.pug
index 21e70304..c14b483b 100644
--- a/src/backend/templates/views/ai.pug
+++ b/src/backend/templates/views/ai.pug
@@ -1,53 +1,60 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "ai"
- - var bannerFirstTitle = "IMPROVED AI'S ON"
- - var bannerSecondTitle = "FORGED ALLIANCE FOREVER"
- - var bannerSubTitle = "COLD BLOODED MACHINES THAT WILL NEVER STOP"
+ - var bannerImage = 'ai'
+ - var bannerFirstTitle = "IMPROVED AI'S ON"
+ - var bannerSecondTitle = 'FORGED ALLIANCE FOREVER'
+ - var bannerSubTitle = 'COLD BLOODED MACHINES THAT WILL NEVER STOP'
-block content
-
- .showcaseBackground
- .descriptionMain
- .descriptionContainer
- h2 Artificial intelligence on FAF
- p Originally, Supreme Commander:FA had very suboptimal AI, being barely able to do anything even trivial. Nonetheless, thanks to the contribution of FAF contributors, now you have 4 quite challenging AIs! Specially on 5x5 and 10x10 land maps, these AIs will show you who's boss! You can download these AIs in the FAF client Mods section.
- .profileMain
- .profileSubGrid.column6
- .profileImage.azraelBG.column4
- .profileContainer.column8
- h2.highlightText Get flooded
- h1 AI-Swarm by AzraelianAngel
- p Swarm is a bit of a middle grounded AI, it is regarded as the most solid economic AI. It tends to be heavy on land but it adapts to air and naval situations!
- a(href='https://forum.faforever.com/topic/53/ai-swarm-ai-mod-for-faforever?_=1646687814769')
- button AI-Swarm DevLog
- .profileSubGrid.column6
- .profileImage.uvesoBG.column4
- .profileContainer.column8
- h2.highlightText Uveso by AI-Uveso
- h1 AI-Uveso by Uveso
- p Adaptive AI with multiple sub AIs in order for players to have control on the behavior of the AI! The sub AIs are Adaptive, Overwhelm, Rush, Experimental and Easy!
- a(href='https://forum.faforever.com/topic/350/ai-uveso-v98-ai-mod-for-faforever')
- button AI-Uveso DevLog
- .profileSubGrid.column6
- .profileImage.relentorBG.column4
- .profileContainer.column8
- h2.highlightText Random number generator?
- h1 RNGAI by Relent0r
- p AI aimed at the players wanting to learn 1v1 gameplay. It provides an avenue to play against something that emulates some of the methods ladder players use!
- a(href='https://forums.faforever.com/viewtopic.php?f=88&t=19149')
- button RGNAI DevLog
- .profileSubGrid.column6
- .profileImage.maudlinBG.column4
- .profileContainer.column8
- h2.highlightText Best 1v1 AI
- h1 M27AI by Maudlin27
- p Adaptive AI, intended for both 1v1 and team games, makes use of advanced tactics for players seeking a challenge.
- a(href='https://forum.faforever.com/topic/2373/ai-development-guide-and-m27ai-v24-devlog')
- button M27AI DevLog
- .descriptionMain
- .descriptionContainer
- h2 Not difficult enough?
- p If AIs aren't a challenge for you, we recommend using the in-lobby option "AIx Cheat Multiplier" which can be found in a game lobby throught Options > AI Options. Putting it to 1.1 or 1.2 should provide a challenge for a great deal of players. This bonus makes the AI cheat, meaning it gets 1.1 resources and buildpower. Making more stuff in less time!
+block content
+ .showcaseBackground
+ .descriptionMain
+ .descriptionContainer
+ h2 Artificial intelligence on FAF
+ p Originally, Supreme Commander:FA had very suboptimal AI, being barely able to do anything even trivial. Nonetheless, thanks to the contribution of FAF contributors, now you have 4 quite challenging AIs! Specially on 5x5 and 10x10 land maps, these AIs will show you who's boss! You can download these AIs in the FAF client Mods section.
+ .profileMain
+ .profileSubGrid.column6
+ .profileImage.azraelBG.column4
+ .profileContainer.column8
+ h2.highlightText Get flooded
+ h1 AI-Swarm by AzraelianAngel
+ p Swarm is a bit of a middle grounded AI, it is regarded as the most solid economic AI. It tends to be heavy on land but it adapts to air and naval situations!
+ a(
+ href='https://forum.faforever.com/topic/53/ai-swarm-ai-mod-for-faforever?_=1646687814769'
+ )
+ button AI-Swarm DevLog
+ .profileSubGrid.column6
+ .profileImage.uvesoBG.column4
+ .profileContainer.column8
+ h2.highlightText Uveso by AI-Uveso
+ h1 AI-Uveso by Uveso
+ p Adaptive AI with multiple sub AIs in order for players to have control on the behavior of the AI! The sub AIs are Adaptive, Overwhelm, Rush, Experimental and Easy!
+ a(
+ href='https://forum.faforever.com/topic/350/ai-uveso-v98-ai-mod-for-faforever'
+ )
+ button AI-Uveso DevLog
+ .profileSubGrid.column6
+ .profileImage.relentorBG.column4
+ .profileContainer.column8
+ h2.highlightText Random number generator?
+ h1 RNGAI by Relent0r
+ p AI aimed at the players wanting to learn 1v1 gameplay. It provides an avenue to play against something that emulates some of the methods ladder players use!
+ a(
+ href='https://forums.faforever.com/viewtopic.php?f=88&t=19149'
+ )
+ button RGNAI DevLog
+ .profileSubGrid.column6
+ .profileImage.maudlinBG.column4
+ .profileContainer.column8
+ h2.highlightText Best 1v1 AI
+ h1 M27AI by Maudlin27
+ p Adaptive AI, intended for both 1v1 and team games, makes use of advanced tactics for players seeking a challenge.
+ a(
+ href='https://forum.faforever.com/topic/2373/ai-development-guide-and-m27ai-v24-devlog'
+ )
+ button M27AI DevLog
+ .descriptionMain
+ .descriptionContainer
+ h2 Not difficult enough?
+ p If AIs aren't a challenge for you, we recommend using the in-lobby option "AIx Cheat Multiplier" which can be found in a game lobby throught Options > AI Options. Putting it to 1.1 or 1.2 should provide a challenge for a great deal of players. This bonus makes the AI cheat, meaning it gets 1.1 resources and buildpower. Making more stuff in less time!
block js
diff --git a/src/backend/templates/views/campaign-missions.pug b/src/backend/templates/views/campaign-missions.pug
index b2c3552d..5b8716dd 100644
--- a/src/backend/templates/views/campaign-missions.pug
+++ b/src/backend/templates/views/campaign-missions.pug
@@ -1,51 +1,48 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "campaign"
- - var bannerFirstTitle = "FIGHT TOGETHER WITH FRIENDS"
- - var bannerSecondTitle = "ON THE NEW CO-OP CAMPAIGN"
- - var bannerSubTitle = "PLUS ALL THE ORIGINAL MISSIONS AND NEW ONES!"
-
-
-block content
- .descriptionMain
- .descriptionContainer
- h2 Enjoy the nostalgia with friends
- p FAF has implemented the co-op feature into the campaign. This means now you can play either of the four original campaigns with up to three friends (four players)! But! That's not all, FAF contributors are also working on creating two new campaigns! One for Seraphim and another one for the coalition! However, be warned that the new campaigns are meant to present a real challenge to seasoned players. If you need some help or have questions, feel free joining the FAF Campaign Development Discord or the FAF Discord!
- a(href='https://discord.gg/ayzAVr9JUV')
- button Campaign Discord
- a(href='https://discord.gg/mXahVSKGVb')
- button Official Discord
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText All 24 missions
- h1 The four original campaigns
- p Enjoy the complete Supreme Commander campaign with up to four players! All 24 missions from the UEF, Cybran, Aeon and Coalition/Forged Alliance Campaigns are already unlocked on the FAF client, all you need to do is hop in with your friends and play together!
- .showcaseImage.originalBG.column6
- .mainShowcaseReverse
- .showcaseImage.seraphimBG.column6
- .showcaseContainer.column6
- h2.highlightText The Seraphim's last stand
- h1 New Seraphim campaign
- p After being defeated by the coalition, the Seraphim must stand up with everything they have and make their last stand! Rise up with the new Seraphim campaign for the hardcore players that want to face the toughest challenge with no backup.
-
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText Want a real challenge?
- h1 New coalition campaign
- p If you want a challenge and yet want to keep playing with coalition factions, then we got some good news for you! Our contributors are also working on a new co-op campaign with the coalition.
- .showcaseImage.coalitionBG.column6
- .mainShowcaseReverse
- .showcaseImage.missionsBG.column6
- .showcaseContainer.column6
- h2.highlightText Extra missions
- h1 New custom missions
- p Even if you have beaten all these campaigns, FAF also has a couple new missions to offer. From rescuing civilians on Theta to crushing a Novax station, you got even more FAF content!
- .descriptionMain
- .descriptionContainer
- h2 Help develop or create your own mission!
- p As you may be thinking, a lot of these new campaigns or missions are being developed by FAF contributors. Therefore, it is possible for you to create one too! Albeit it can be challenging at first if you don't have much experience, many FAF contributors can guide and help you at first so you can create your own custom missions! Just join the Campaign Development Discord and ask around!
- a(href='https://discord.gg/ayzAVr9JUV')
- button Campaign Discord
+ - var bannerImage = 'campaign'
+ - var bannerFirstTitle = 'FIGHT TOGETHER WITH FRIENDS'
+ - var bannerSecondTitle = 'ON THE NEW CO-OP CAMPAIGN'
+ - var bannerSubTitle = 'PLUS ALL THE ORIGINAL MISSIONS AND NEW ONES!'
+block content
+ .descriptionMain
+ .descriptionContainer
+ h2 Enjoy the nostalgia with friends
+ p FAF has implemented the co-op feature into the campaign. This means now you can play either of the four original campaigns with up to three friends (four players)! But! That's not all, FAF contributors are also working on creating two new campaigns! One for Seraphim and another one for the coalition! However, be warned that the new campaigns are meant to present a real challenge to seasoned players. If you need some help or have questions, feel free joining the FAF Campaign Development Discord or the FAF Discord!
+ a(href='https://discord.gg/ayzAVr9JUV')
+ button Campaign Discord
+ a(href='https://discord.gg/mXahVSKGVb')
+ button Official Discord
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText All 24 missions
+ h1 The four original campaigns
+ p Enjoy the complete Supreme Commander campaign with up to four players! All 24 missions from the UEF, Cybran, Aeon and Coalition/Forged Alliance Campaigns are already unlocked on the FAF client, all you need to do is hop in with your friends and play together!
+ .showcaseImage.originalBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.seraphimBG.column6
+ .showcaseContainer.column6
+ h2.highlightText The Seraphim's last stand
+ h1 New Seraphim campaign
+ p After being defeated by the coalition, the Seraphim must stand up with everything they have and make their last stand! Rise up with the new Seraphim campaign for the hardcore players that want to face the toughest challenge with no backup.
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText Want a real challenge?
+ h1 New coalition campaign
+ p If you want a challenge and yet want to keep playing with coalition factions, then we got some good news for you! Our contributors are also working on a new co-op campaign with the coalition.
+ .showcaseImage.coalitionBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.missionsBG.column6
+ .showcaseContainer.column6
+ h2.highlightText Extra missions
+ h1 New custom missions
+ p Even if you have beaten all these campaigns, FAF also has a couple new missions to offer. From rescuing civilians on Theta to crushing a Novax station, you got even more FAF content!
+ .descriptionMain
+ .descriptionContainer
+ h2 Help develop or create your own mission!
+ p As you may be thinking, a lot of these new campaigns or missions are being developed by FAF contributors. Therefore, it is possible for you to create one too! Albeit it can be challenging at first if you don't have much experience, many FAF contributors can guide and help you at first so you can create your own custom missions! Just join the Campaign Development Discord and ask around!
+ a(href='https://discord.gg/ayzAVr9JUV')
+ button Campaign Discord
diff --git a/src/backend/templates/views/clans.pug b/src/backend/templates/views/clans.pug
index 1c456f80..78090c82 100644
--- a/src/backend/templates/views/clans.pug
+++ b/src/backend/templates/views/clans.pug
@@ -3,77 +3,79 @@ include ../mixins/flash-messages
block bannerMixin
block content
+ .clanBackground
+ .mainClanContainer
+ .clanContainer.column12.centerYourself
+ +flash-messages(flash)
+ p Search FAF Clans
+ #errorLog
+ input#input(
+ onkeyup='pressEnter(event)',
+ type='text',
+ placeholder='Clan TAG'
+ )
+ #searchbar
+ .searchBar
+ ul
+ #placeMe
+ ul#clearSearch.clearButton.appearWhenSearching
+ li.fas.fa-trash-alt
+ #searchResults.mainClanContainer.clanBorder.appearWhenSearching
+ .clanContainer.column6
+ .clanItem
+ ul Clan Name
+ li#clanNameSearch
+ .clanContainer.column1
+ .clanItem
+ ul TAG
+ li#clanTAGSearch
+ .clanContainer.column3
+ .clanItem
+ ul Clan Leader
+ li#clanPlayerSearch
+ .clanContainer.column2
+ .clanItem
+ ul Population
+ li#clanPopulationSearch
+ .mainClanContainer
+ //DO NOT Change the order of these without changing the js. For them to work, they need to be in this specific order
+ .clanContainer.clanButton.column12
+ ul
+ li.pageButton.exhaustedButton(onclick='pageChange(0)') First
+ li.pageButton(onclick='pageChange(lastPage)') Last
+ ul
+ li.pageButton.exhaustedButton(
+ onclick='pageChange(pageNumber - 1)'
+ ) Previous
+ li.pageButton(onclick='pageChange(pageNumber + 1)') Next
- .clanBackground
- .mainClanContainer
- .clanContainer.column12.centerYourself
- +flash-messages(flash)
- p Search FAF Clans
- #errorLog
- input#input(onkeyup=`pressEnter(event)` type='text' placeholder='Clan TAG')
- #searchbar
- .searchBar
- ul
- #placeMe
- ul#clearSearch.clearButton.appearWhenSearching
- li.fas.fa-trash-alt
- .mainClanContainer.clanBorder#searchResults.appearWhenSearching
-
- .clanContainer.column6
- .clanItem
- ul Clan Name
- li#clanNameSearch
- .clanContainer.column1
- .clanItem
- ul TAG
- li#clanTAGSearch
- .clanContainer.column3
- .clanItem
- ul Clan Leader
- li#clanPlayerSearch
- .clanContainer.column2
- .clanItem
- ul Population
- li#clanPopulationSearch
- .mainClanContainer
-
- //DO NOT Change the order of these without changing the js. For them to work, they need to be in this specific order
- .clanContainer.clanButton.column12
- ul
- li(onclick= `pageChange(0)`).pageButton.exhaustedButton First
- li(onclick= `pageChange(lastPage)`).pageButton Last
- ul
- li(onclick= `pageChange(pageNumber - 1)`).pageButton.exhaustedButton Previous
- li(onclick= `pageChange(pageNumber + 1)`).pageButton Next
-
- .mainClanContainer.clanBorder
-
-
- .clanContainer.column6
- .clanItem
- ul Clan Name
- li#clanName
- .clanContainer.column1
- .clanItem
- ul TAG
- li#clanTAG
- .clanContainer.column3
- .clanItem
- ul#spawnPlayer Clan Leader
- li#clanPlayer
- .clanContainer.column2
- .clanItem
- ul Population
- li#clanPopulation
- .mainClanContainer
- .clanContainer.clanButton.column12
- ul
- li(onclick= `pageChange(0)`).pageButton.exhaustedButton First
- li(onclick= `pageChange(lastPage)`).pageButton Last
- ul
- li(onclick= `pageChange(pageNumber - 1)`).pageButton.exhaustedButton Previous
- li(onclick= `pageChange(pageNumber + 1)`).pageButton Next
+ .mainClanContainer.clanBorder
+ .clanContainer.column6
+ .clanItem
+ ul Clan Name
+ li#clanName
+ .clanContainer.column1
+ .clanItem
+ ul TAG
+ li#clanTAG
+ .clanContainer.column3
+ .clanItem
+ ul#spawnPlayer Clan Leader
+ li#clanPlayer
+ .clanContainer.column2
+ .clanItem
+ ul Population
+ li#clanPopulation
+ .mainClanContainer
+ .clanContainer.clanButton.column12
+ ul
+ li.pageButton.exhaustedButton(onclick='pageChange(0)') First
+ li.pageButton(onclick='pageChange(lastPage)') Last
+ ul
+ li.pageButton.exhaustedButton(
+ onclick='pageChange(pageNumber - 1)'
+ ) Previous
+ li.pageButton(onclick='pageChange(pageNumber + 1)') Next
block js
- script( src=webpackAssetJS('clans'))
-
+ script(src=webpackAssetJS('clans'))
diff --git a/src/backend/templates/views/clans/accept_invite.pug b/src/backend/templates/views/clans/accept_invite.pug
index bf5ab1a6..612b4f51 100644
--- a/src/backend/templates/views/clans/accept_invite.pug
+++ b/src/backend/templates/views/clans/accept_invite.pug
@@ -2,9 +2,8 @@ extends ../../layouts/default
block bannerMixin
block content
.row
- .col-md-12
- h1.account-title Accept invitation
- h4.account-subtitle.text-center Click the button below to accept the invitation to join #{clanName}
- a(href=acceptURL).faf-button Join #{clanName}
+ .col-md-12
+ h1.account-title Accept invitation
+ h4.account-subtitle.text-center Click the button below to accept the invitation to join #{ clanName }
+ a.faf-button(href=acceptURL) Join #{ clanName }
br
-
diff --git a/src/backend/templates/views/clans/clan.pug b/src/backend/templates/views/clans/clan.pug
index 571da6d2..eadd3448 100644
--- a/src/backend/templates/views/clans/clan.pug
+++ b/src/backend/templates/views/clans/clan.pug
@@ -1,7 +1,7 @@
extends ../../layouts/default
block content
#clan-view
- a(href="/clans") <- OVERVIEW
+ a(href='/clans') <- OVERVIEW
.gridMainContainer
@@ -9,22 +9,22 @@ block content
table#clan-info
tr
td NAME
- td [#{clan.tag}] #{clan.name}
+ td [#{ clan.tag }] #{ clan.name }
tr
td LEADER
if clan.leader
- td #{clan.leader.name} 👑
+ td #{ clan.leader.name } 👑
else
td -
tr
td FOUNDER
if clan.founder
- td #{clan.founder.name}
+ td #{ clan.founder.name }
else
td -
tr
td FOUNDED
- td #{clan.createTime}
+ td #{ clan.createTime }
tr
td JOIN
if clan.requiresInvitation
@@ -32,13 +32,13 @@ block content
else
td Free For All
.column8
- div(style='white-space:pre-wrap') #{clan.description}
+ div(style='white-space: pre-wrap') #{ clan.description }
.column12
if canLeave
- a(href='/clans/leave').faf-button Leave Clan
+ a.faf-button(href='/clans/leave') Leave Clan
if isLeader
- a(href='/clans/invite').faf-button Create Invite
- a(href='/clans/manage').faf-button Update Clan
+ a.faf-button(href='/clans/invite') Create Invite
+ a.faf-button(href='/clans/manage') Update Clan
.column12
table#clan-members
thead
@@ -49,10 +49,13 @@ block content
tbody
each member in clan.memberships
tr
- td #{member.name}
- td #{member.joinedAt}
+ td #{ member.name }
+ td #{ member.joinedAt }
td
if isLeader && member.membershipId !== userMembershipId
- a(href='/clans/kick/' + member.membershipId onclick="return confirm('Kick?')").action-link Kick
+ a.action-link(
+ href='/clans/kick/' + member.membershipId,
+ onclick='return confirm(\'Kick?\')'
+ ) Kick
block js
- script( src=webpackAssetJS('clan'))
+ script(src=webpackAssetJS('clan'))
diff --git a/src/backend/templates/views/clans/create.pug b/src/backend/templates/views/clans/create.pug
index f93bccec..de281872 100644
--- a/src/backend/templates/views/clans/create.pug
+++ b/src/backend/templates/views/clans/create.pug
@@ -3,30 +3,55 @@ include ../../mixins/flash-error
include ../../mixins/form/account
block bannerMixin
block content
- .containerCenter.text-center
- .row
- .col-md-12
- h1 Create a clan
- div
- | You can create your own clan, and then invite other players to join it.
-
- | Be sure to 
- a(href='/rules') review the rules
- | before naming your clan!
-
- | Offensive clan names will result in an immediate sanction
+ .containerCenter.text-center
+ .row
+ .col-md-12
+ h1 Create a clan
+ div
+ | You can create your own clan, and then invite other players to join it.
+
+ | Be sure to 
+ a(href='/rules') review the rules
+ |
+ | before naming your clan!
+
+ | Offensive clan names will result in an immediate sanction
- .row
- .col-md-offset-3.col-md-6
- +flash-error(errors)
- .clanManagement
- .column12
- form(method='post', action="/clans/create", data-toggle="validator")
- input(type='text' name='clan_name' value=clan_name placeholder='Clan name' title='Name' required='required')
- br
- input(type='text' required='required' name='clan_tag' title='Tag' value=clan_tag placeholder='TAG')
- br
- textarea(rows='12' name='clan_description' title='description' required='required' placeholder='The description players will see when they look your clan') #{clan_description}
- br
+ .row
+ .col-md-offset-3.col-md-6
+ +flash-error(errors)
+ .clanManagement
+ .column12
+ form(
+ method='post',
+ action='/clans/create',
+ data-toggle='validator'
+ )
+ input(
+ type='text',
+ name='clan_name',
+ value=clan_name,
+ placeholder='Clan name',
+ title='Name',
+ required='required'
+ )
+ br
+ input(
+ type='text',
+ required='required',
+ name='clan_tag',
+ title='Tag',
+ value=clan_tag,
+ placeholder='TAG'
+ )
+ br
+ textarea(
+ rows='12',
+ name='clan_description',
+ title='description',
+ required='required',
+ placeholder='The description players will see when they look your clan'
+ ) #{ clan_description }
+ br
- button(type='submit').bigButton Create your Clan
+ button.bigButton(type='submit') Create your Clan
diff --git a/src/backend/templates/views/clans/invite.pug b/src/backend/templates/views/clans/invite.pug
index f7707335..140e8136 100644
--- a/src/backend/templates/views/clans/invite.pug
+++ b/src/backend/templates/views/clans/invite.pug
@@ -3,9 +3,11 @@ include ../../mixins/flash-error
include ../../mixins/form/account
block bannerMixin
block css
- link(href="/styles/awesomplete.css?version=" + Date.now(), rel="stylesheet")
+ link(
+ href='/styles/awesomplete.css?version=' + Date.now(),
+ rel='stylesheet'
+ )
block content
-
.clanManagement
.column12
+flash-error(errors)
@@ -15,15 +17,20 @@ block content
h2 Invite players
if link
.row
- p Invite created for #{invited_player}
- button(data-href=link id='invitationLink') click to copy link
- form(method='post' action="/clans/invite")
+ p Invite created for #{ invited_player }
+ button#invitationLink(data-href=link) click to copy link
+ form(method='post', action='/clans/invite')
.row.inline-panel
- input(type='text' id='invited_player' name='invited_player' value=invited_player placeholder='Player name' style="margin-left:5px;margin-right:5px").form-control
+ input#invited_player.form-control(
+ type='text',
+ name='invited_player',
+ value=invited_player,
+ placeholder='Player name',
+ style='margin-left: 5px; margin-right: 5px'
+ )
button(type='submit') Invite
br
br
-
block js
script(src=webpackAssetJS('clan-invite'))
diff --git a/src/backend/templates/views/clans/leave.pug b/src/backend/templates/views/clans/leave.pug
index 2782fe69..ea804514 100644
--- a/src/backend/templates/views/clans/leave.pug
+++ b/src/backend/templates/views/clans/leave.pug
@@ -3,7 +3,6 @@ include ../../mixins/flash-error
include ../../mixins/form/account
block bannerMixin
block content
-
.clanManagement
.column12
+flash-error(errors)
@@ -11,8 +10,7 @@ block content
.clanManagement
.column12
h2 Leave Clan
- form(method='post' action="/clans/leave")
+ form(method='post', action='/clans/leave')
.row.inline-panel
button(type='submit') Confirm
br
-
diff --git a/src/backend/templates/views/clans/manage.pug b/src/backend/templates/views/clans/manage.pug
index 013aec16..f2d8ed7d 100644
--- a/src/backend/templates/views/clans/manage.pug
+++ b/src/backend/templates/views/clans/manage.pug
@@ -3,19 +3,42 @@ include ../../mixins/flash-error
include ../../mixins/form/account
block bannerMixin
block content
-
.clanManagement
.column12
+flash-error(errors)
.column12
h2 Clan Settings
- form(method='post', action="/clans/update", data-toggle="validator")
- input(type='text' name='clan_name' value=clan_name placeholder='Clan name' title='Name' required='required')
+ form(
+ method='post',
+ action='/clans/update',
+ data-toggle='validator'
+ )
+ input(
+ type='text',
+ name='clan_name',
+ value=clan_name,
+ placeholder='Clan name',
+ title='Name',
+ required='required'
+ )
br
- input(type='text' required='required' name='clan_tag' title='Tag' value=clan_tag placeholder='TAG')
+ input(
+ type='text',
+ required='required',
+ name='clan_tag',
+ title='Tag',
+ value=clan_tag,
+ placeholder='TAG'
+ )
br
- textarea(rows='12' name='clan_description' title='description' required='required' placeholder='The description players will see when they look your clan') #{clan_description}
+ textarea(
+ rows='12',
+ name='clan_description',
+ title='description',
+ required='required',
+ placeholder='The description players will see when they look your clan'
+ ) #{ clan_description }
br
button(type='submit') Update Clan Settings
@@ -28,6 +51,10 @@ block content
div The settings below CANNOT be undone. Do not touch these settings unless you are sure about what you are doing.
br
- form(method='post',action="/clans/destroy", onsubmit="return confirm('THIS OPERATION IS DEFINITIVE. Press OK to confirm you want to delete your clan');")
+ form(
+ method='post',
+ action='/clans/destroy',
+ onsubmit='return confirm(\'THIS OPERATION IS DEFINITIVE. Press OK to confirm you want to delete your clan\');'
+ )
button(type='submit') Delete my clan
br
diff --git a/src/backend/templates/views/competitive_nav.pug b/src/backend/templates/views/competitive_nav.pug
index b259d869..8ef06d89 100644
--- a/src/backend/templates/views/competitive_nav.pug
+++ b/src/backend/templates/views/competitive_nav.pug
@@ -1,11 +1,11 @@
extends ../layouts/default
block content
- .container
- ul.nav.nav-tabs.competitive-nav
- h1.text-center Competitive Supreme Commander: Forged Alliance
- h4.text-center The FAF community hosts regular tournaments for all levels of play
- each link in cNavLinks
- li(class=(cSection == link.key ? 'active' : null)): a(href=link.href)= link.label
- .container
- block cContent
+ .container
+ ul.nav.nav-tabs.competitive-nav
+ h1.text-center Competitive Supreme Commander: Forged Alliance
+ h4.text-center The FAF community hosts regular tournaments for all levels of play
+ each link in cNavLinks
+ li(class=cSection == link.key ? 'active' : null): a(href=link.href)= link.label
+ .container
+ block cContent
diff --git a/src/backend/templates/views/content-creators.pug b/src/backend/templates/views/content-creators.pug
index 8cc4bb9e..0d82e21f 100644
--- a/src/backend/templates/views/content-creators.pug
+++ b/src/backend/templates/views/content-creators.pug
@@ -1,12 +1,11 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "contentcreator"
- - var bannerFirstTitle = " FROM ENTERTAINING CASTERS TO "
- - var bannerSecondTitle = "TOP PLAYERS ANALYZING MATCHES"
- - var bannerSubTitle = "EITHER LAUGH OR LEARN WITH THEM, MAYBE BOTH?"
+ - var bannerImage = 'contentcreator'
+ - var bannerFirstTitle = ' FROM ENTERTAINING CASTERS TO '
+ - var bannerSecondTitle = 'TOP PLAYERS ANALYZING MATCHES'
+ - var bannerSubTitle = 'EITHER LAUGH OR LEARN WITH THEM, MAYBE BOTH?'
block content
- #contentCreatorWordpress
-
-block js
+ #contentCreatorWordpress
- script(src=webpackAssetJS('content-creators'))
+block js
+ script(src=webpackAssetJS('content-creators'))
diff --git a/src/backend/templates/views/contribution.pug b/src/backend/templates/views/contribution.pug
index 1ed71781..bc4aa0bc 100644
--- a/src/backend/templates/views/contribution.pug
+++ b/src/backend/templates/views/contribution.pug
@@ -1,55 +1,67 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "contribution"
- - var bannerFirstTitle = "LEND A HAND TODAY"
- - var bannerSecondTitle = "HELP THE FAF COMMUNITY"
- - var bannerSubTitle = ""
+ - var bannerImage = 'contribution'
+ - var bannerFirstTitle = 'LEND A HAND TODAY'
+ - var bannerSecondTitle = 'HELP THE FAF COMMUNITY'
+ - var bannerSubTitle = ''
block content
+ .showcaseBackground
+ .descriptionMain
+ .descriptionContainer
+ H2 READY TO DO YOUR DUTY SOLDIER?
+ p FAF isn't being maintained by any company or business. It is being built upon the contributions of hundreds of individuals, all with different talents, giving a bit of their time and expertise to develop FAF more and more. Whether you are a programmer, graphic designer, content creator or multilingual, FAF has a space for you to use your skills for the betterment of the community!
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText Fixing bugs, killing lag
+ h1 Game development
+ p Want to help fix a game issue? Create your own Co-op mission? Then you can help with development on Github. To start, join our discord so that you can talk with the correct dev and invite you to our Zulip. It is recommended (but not necessary!) knowing some Lua for game developlment, Python for the server work or Java for the FAF client.
+ a(target='_blank', href='https://discord.gg/mXahVSKGVb')
+ button Join our Discord
+ a(target='_blank', href='https://github.com/faforever/')
+ button FAF repository
+ .showcaseImage.developmentBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.mapsModsBG.column6
+ .showcaseContainer.column6
+ h2.highlightText Infinite possibilities
+ h1 Creating maps and mods
+ p Make your wildest dreams come true and create the twin barrel Monkeylord or release your inner artist and create the Mona Lisa of Maps. Our vaults are always open for new and exciting maps and mods! If you have doubts about where to start or are stuck, we recommend joining our Discord for answers!
+ a(
+ target='_blank',
+ href='https://wiki.faforever.com/en/Development/Mapping/FA-Forever-Map-Editor'
+ )
+ button Map Editor Guide
+ a(
+ target='_blank',
+ href='https://wiki.faforever.com/en/Development/Modding/Modding'
+ )
+ button Mod Making Guide
+ a(
+ target='_blank',
+ href='https://www.youtube.com/playlist?list=PL0nxuIUIjpFvqJ5i1HfPwoA8FnBCtGLWn'
+ )
+ button Youtube Playlist
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText Become our guinea pig
+ h1 Testing & bug reports
+ p Want to be the first one to try the latest features? Then you can help as a tester for balance patches, client updates and by testing the latest new mods or AIs from our developers. Just join our Discord and grab the tester role!
+ a(
+ target='_blank',
+ href='https://discord.com/channels/197033481883222026/1111359811230113814'
+ )
+ button Join our discord
-
- .showcaseBackground
- .descriptionMain
- .descriptionContainer
- H2 READY TO DO YOUR DUTY SOLDIER?
- p FAF isn't being maintained by any company or business. It is being built upon the contributions of hundreds of individuals, all with different talents, giving a bit of their time and expertise to develop FAF more and more. Whether you are a programmer, graphic designer, content creator or multilingual, FAF has a space for you to use your skills for the betterment of the community!
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText Fixing bugs, killing lag
- h1 Game development
- p Want to help fix a game issue? Create your own Co-op mission? Then you can help with development on Github. To start, join our discord so that you can talk with the correct dev and invite you to our Zulip. It is recommended (but not necessary!) knowing some Lua for game developlment, Python for the server work or Java for the FAF client.
- a(target='_blank' href='https://discord.gg/mXahVSKGVb')
- button Join our Discord
- a(target='_blank' href='https://github.com/faforever/')
- button FAF repository
- .showcaseImage.developmentBG.column6
- .mainShowcaseReverse
- .showcaseImage.mapsModsBG.column6
- .showcaseContainer.column6
- h2.highlightText Infinite possibilities
- h1 Creating maps and mods
- p Make your wildest dreams come true and create the twin barrel Monkeylord or release your inner artist and create the Mona Lisa of Maps. Our vaults are always open for new and exciting maps and mods! If you have doubts about where to start or are stuck, we recommend joining our Discord for answers!
- a(target='_blank' href='https://wiki.faforever.com/en/Development/Mapping/FA-Forever-Map-Editor')
- button Map Editor Guide
- a(target='_blank' href='https://wiki.faforever.com/en/Development/Modding/Modding')
- button Mod Making Guide
- a(target='_blank' href='https://www.youtube.com/playlist?list=PL0nxuIUIjpFvqJ5i1HfPwoA8FnBCtGLWn')
- button Youtube Playlist
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText Become our guinea pig
- h1 Testing & bug reports
- p Want to be the first one to try the latest features? Then you can help as a tester for balance patches, client updates and by testing the latest new mods or AIs from our developers. Just join our Discord and grab the tester role!
- a(target='_blank' href='https://discord.com/channels/197033481883222026/1111359811230113814')
- button Join our discord
-
- .showcaseImage.testerBG.column6
- .mainShowcaseReverse
- .showcaseImage.artworkBG.column6
- .showcaseContainer.column6
- h2.highlightText Show your skills
- h1 Graphic design and promotion
- p Everytime there is a new event, we need anything from pixel art client avatars, poster graphics, tourney logos to video animations for our Twitch casters. No matter the art style, we'll take any artists that want to contribute with their FAF related artwork and skills! Just join the discord and look for the promotions team!
- a(target='_blank' href='https://discord.com/channels/197033481883222026/1108833863817498656')
- button Join our Discord
-
+ .showcaseImage.testerBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.artworkBG.column6
+ .showcaseContainer.column6
+ h2.highlightText Show your skills
+ h1 Graphic design and promotion
+ p Everytime there is a new event, we need anything from pixel art client avatars, poster graphics, tourney logos to video animations for our Twitch casters. No matter the art style, we'll take any artists that want to contribute with their FAF related artwork and skills! Just join the discord and look for the promotions team!
+ a(
+ target='_blank',
+ href='https://discord.com/channels/197033481883222026/1108833863817498656'
+ )
+ button Join our Discord
diff --git a/src/backend/templates/views/donation.pug b/src/backend/templates/views/donation.pug
index 2d42c1e7..8af00f1d 100644
--- a/src/backend/templates/views/donation.pug
+++ b/src/backend/templates/views/donation.pug
@@ -1,31 +1,24 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "donation"
- - var bannerFirstTitle = "LETS KEEP THE FOREVER IN"
- - var bannerSecondTitle = "FORGED ALLIANCE FOREVER!"
- - var bannerSubTitle = "AND GET SOME REWARDS!"
+ - var bannerImage = 'donation'
+ - var bannerFirstTitle = 'LETS KEEP THE FOREVER IN'
+ - var bannerSecondTitle = 'FORGED ALLIANCE FOREVER!'
+ - var bannerSubTitle = 'AND GET SOME REWARDS!'
block content
+ figure.highcharts-figure(aria-label='Donation pie chart')
+ #container
-
-
-
- figure.highcharts-figure(aria-label='Donation pie chart')
- #container
-
- .descriptionMain
- .descriptionContainer
- p As a non-profit open source project we are reliant on donations to keep servers running, pay for other infrastructure costs and create a competitive scene for FAF. Donations are used exclusively for continued development and support of FAF. Through our Patreon, you can decide where your funds will go (Free to use, Tournament prizes or Infrastructure costs). We are extremely grateful for your aid!
- h2 You can Donate to FAF through
- a(href='https://www.patreon.com/faf' target='_blank')
- button
- img(src='/images/logos/patreonBlack.png' alt='Patreon Logo')
- a(href='https://www.paypal.com/paypalme/faforever' target='_blank')
- button
- img(src='/images/logos/paypal.png' alt='Paypal Logo')
-
-
+ .descriptionMain
+ .descriptionContainer
+ p As a non-profit open source project we are reliant on donations to keep servers running, pay for other infrastructure costs and create a competitive scene for FAF. Donations are used exclusively for continued development and support of FAF. Through our Patreon, you can decide where your funds will go (Free to use, Tournament prizes or Infrastructure costs). We are extremely grateful for your aid!
+ h2 You can Donate to FAF through
+ a(href='https://www.patreon.com/faf', target='_blank')
+ button
+ img(src='/images/logos/patreonBlack.png', alt='Patreon Logo')
+ a(href='https://www.paypal.com/paypalme/faforever', target='_blank')
+ button
+ img(src='/images/logos/paypal.png', alt='Paypal Logo')
block js
- script(src=webpackAssetJS('donation'))
-
+ script(src=webpackAssetJS('donation'))
diff --git a/src/backend/templates/views/errors/404.pug b/src/backend/templates/views/errors/404.pug
index 857b503d..afc40e53 100644
--- a/src/backend/templates/views/errors/404.pug
+++ b/src/backend/templates/views/errors/404.pug
@@ -1,9 +1,8 @@
extends ../../layouts/default
block bannerData
- - var bannerImage = "404"
- - var bannerFirstTitle = "404 Error"
- - var bannerSecondTitle = ""
- - var bannerSubTitle = "Sorry, the page you requested can't be found."
+ - var bannerImage = '404'
+ - var bannerFirstTitle = '404 Error'
+ - var bannerSecondTitle = ''
+ - var bannerSubTitle = "Sorry, the page you requested can't be found."
block content
-
diff --git a/src/backend/templates/views/errors/500.pug b/src/backend/templates/views/errors/500.pug
index edce3514..3e104a42 100644
--- a/src/backend/templates/views/errors/500.pug
+++ b/src/backend/templates/views/errors/500.pug
@@ -1,8 +1,7 @@
extends ../../layouts/default
block bannerData
- - var bannerImage = "404"
- - var bannerFirstTitle = "500 Error"
- - var bannerSecondTitle = ""
- - var bannerSubTitle = "Sorry, the site has encountered an error"
+ - var bannerImage = '404'
+ - var bannerFirstTitle = '500 Error'
+ - var bannerSecondTitle = ''
+ - var bannerSubTitle = 'Sorry, the site has encountered an error'
block content
-
diff --git a/src/backend/templates/views/errors/503-known-issue.pug b/src/backend/templates/views/errors/503-known-issue.pug
index 9280879d..7ae1af69 100644
--- a/src/backend/templates/views/errors/503-known-issue.pug
+++ b/src/backend/templates/views/errors/503-known-issue.pug
@@ -1,8 +1,7 @@
extends ../../layouts/default
block bannerData
- - var bannerImage = "tutorial"
- - var bannerFirstTitle = "Temporarily Disabled"
- - var bannerSecondTitle = ""
- - var bannerSubTitle = "Sorry commanders, we failed to build enough pgens and are now in a tech upgrade"
+ - var bannerImage = 'tutorial'
+ - var bannerFirstTitle = 'Temporarily Disabled'
+ - var bannerSecondTitle = ''
+ - var bannerSubTitle = 'Sorry commanders, we failed to build enough pgens and are now in a tech upgrade'
block content
-
diff --git a/src/backend/templates/views/faf-teams.pug b/src/backend/templates/views/faf-teams.pug
index 051b1a70..e403a1d3 100644
--- a/src/backend/templates/views/faf-teams.pug
+++ b/src/backend/templates/views/faf-teams.pug
@@ -1,62 +1,74 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "fafteams"
- - var bannerFirstTitle = "THE PEOPLE THAT"
- - var bannerSecondTitle = "KEEP FAF RUNNING"
- - var bannerSubTitle = "THE STRUCTURE TO THE CHAOS"
+ - var bannerImage = 'fafteams'
+ - var bannerFirstTitle = 'THE PEOPLE THAT'
+ - var bannerSecondTitle = 'KEEP FAF RUNNING'
+ - var bannerSubTitle = 'THE STRUCTURE TO THE CHAOS'
block content
- .descriptionMain
- .descriptionContainer
- h2 FAF Teams
- p The teams below are all key parts of not only keeping FAF alive, but to give it structure. FAF has a "distributed" system of power. For example, the balance team decides balance decisions. They do not have any upper management to answer to. Therefore, each team leader and its members can work their vision to reality without worrying too much about bureaucracy.
- //.descriptionText
- p The teams below are all key parts of not only keeping FAF alive, but to continuously develop it, promote FAF and its events, create a competitive scene, maintain the files and longevity of FAF, keep the balance fun and fair, make sure community spaces are civil and healthy and much more! FAF is kept alive thanks to the work of multiple contributors, all adding another bit to make the game better everyday!
- br
- a(href='https://wiki.faforever.com/en/FAF-Teams' target='_blank')
- button Learn More
-
- .teamMain#insertWordpress
- .teamSelection.column4
- h1 PROMOTIONS TEAM
- img(src='/images/fafteams/promo.png' alt='Promotions Team Logo')
- .teamSelection.column4
- h1 TRAINER TEAM
- img(src='/images/fafteams/trainer.png' alt='Trainer Team Logo')
- .teamSelection.column4
- h1 FAFLIVE TEAM
- img(src='/images/fafteams/game.png' alt='FAFLive Team Logo')
- .teamSelection.column4
- h1 TOURNAMENT TEAM
- img(src='/images/fafteams/tournament.png' alt='tournament Team Logo')
- .teamSelection.column4
- h1 matchmaking TEAM
- img(src='/images/fafteams/matchmaking.png' alt='matchmaking Team Logo')
- .teamSelection.column4
- h1 balance TEAM
- img(src='/images/fafteams/balance.png' alt='balance Team Logo')
- .teamSelection.column4
- h1 creative TEAM
- img(src='/images/fafteams/creative.png' alt='creative Team Logo')
- .teamSelection.column4
- h1 moderation TEAM
- img(src='/images/fafteams/moderation.png' alt='moderation Team Logo')
- .teamSelection.column4
- h1 devops TEAM
- img(src='/images/fafteams/devops.png' alt='devops Team Logo')
- .descriptionMain
- .descriptionContainer
- H2 The Association
- p Albeit FAF teams have a lot of free reign on their duties, there is still a group above them: The Association. It is registered as a non-profit voluntary association registered in Denmark. It is the legal entity that owns FAForever (FAF doesn't own Supreme Commander or its intellectual property). In the early months of every year there is a democratic general election to choose the board members (the people that make significant decisions for the community/FAForever). The board is formed by the contributors with the most renown within the community.
- a(href='https://forum.faforever.com/topic/2347/what-is-the-association')
- button Learn More
- a(href='https://docs.google.com/document/d/1hvEtv6hCD3-ZUhTHDzyYpNAcHc8PYY3BMT_-UDyc0uM/edit')
- button Statuses of the Association
- a(href='https://forum.faforever.com/topic/2346/how-to-become-a-member-of-the-association')
- button How to Join the Association
-
+ .descriptionMain
+ .descriptionContainer
+ h2 FAF Teams
+ p The teams below are all key parts of not only keeping FAF alive, but to give it structure. FAF has a "distributed" system of power. For example, the balance team decides balance decisions. They do not have any upper management to answer to. Therefore, each team leader and its members can work their vision to reality without worrying too much about bureaucracy.
+ //.descriptionText
+ p The teams below are all key parts of not only keeping FAF alive, but to continuously develop it, promote FAF and its events, create a competitive scene, maintain the files and longevity of FAF, keep the balance fun and fair, make sure community spaces are civil and healthy and much more! FAF is kept alive thanks to the work of multiple contributors, all adding another bit to make the game better everyday!
+ br
+ a(href='https://wiki.faforever.com/en/FAF-Teams' target='_blank')
+ button Learn More
+ #insertWordpress.teamMain
+ .teamSelection.column4
+ h1 PROMOTIONS TEAM
+ img(src='/images/fafteams/promo.png', alt='Promotions Team Logo')
+ .teamSelection.column4
+ h1 TRAINER TEAM
+ img(src='/images/fafteams/trainer.png', alt='Trainer Team Logo')
+ .teamSelection.column4
+ h1 FAFLIVE TEAM
+ img(src='/images/fafteams/game.png', alt='FAFLive Team Logo')
+ .teamSelection.column4
+ h1 TOURNAMENT TEAM
+ img(
+ src='/images/fafteams/tournament.png',
+ alt='tournament Team Logo'
+ )
+ .teamSelection.column4
+ h1 matchmaking TEAM
+ img(
+ src='/images/fafteams/matchmaking.png',
+ alt='matchmaking Team Logo'
+ )
+ .teamSelection.column4
+ h1 balance TEAM
+ img(src='/images/fafteams/balance.png', alt='balance Team Logo')
+ .teamSelection.column4
+ h1 creative TEAM
+ img(src='/images/fafteams/creative.png', alt='creative Team Logo')
+ .teamSelection.column4
+ h1 moderation TEAM
+ img(
+ src='/images/fafteams/moderation.png',
+ alt='moderation Team Logo'
+ )
+ .teamSelection.column4
+ h1 devops TEAM
+ img(src='/images/fafteams/devops.png', alt='devops Team Logo')
+ .descriptionMain
+ .descriptionContainer
+ H2 The Association
+ p Albeit FAF teams have a lot of free reign on their duties, there is still a group above them: The Association. It is registered as a non-profit voluntary association registered in Denmark. It is the legal entity that owns FAForever (FAF doesn't own Supreme Commander or its intellectual property). In the early months of every year there is a democratic general election to choose the board members (the people that make significant decisions for the community/FAForever). The board is formed by the contributors with the most renown within the community.
+ a(
+ href='https://forum.faforever.com/topic/2347/what-is-the-association'
+ )
+ button Learn More
+ a(
+ href='https://docs.google.com/document/d/1hvEtv6hCD3-ZUhTHDzyYpNAcHc8PYY3BMT_-UDyc0uM/edit'
+ )
+ button Statuses of the Association
+ a(
+ href='https://forum.faforever.com/topic/2346/how-to-become-a-member-of-the-association'
+ )
+ button How to Join the Association
block js
- script(src=webpackAssetJS('faf-teams'))
-
+ script(src=webpackAssetJS('faf-teams'))
diff --git a/src/backend/templates/views/index.pug b/src/backend/templates/views/index.pug
index 346348f1..c858badf 100644
--- a/src/backend/templates/views/index.pug
+++ b/src/backend/templates/views/index.pug
@@ -1,122 +1,118 @@
extends ../layouts/default
-block bannerData
- - var bannerImage = "home"
- - var bannerFirstTitle = "AN RTS CLASSIC REFORGED"
- - var bannerSecondTitle = "MASSIVE SCALE WARFARE"
- - var bannerSubTitle = "MANY COMMANDERS PLAYING RIGHT NOW"
+block bannerData
+ - var bannerImage = 'home'
+ - var bannerFirstTitle = 'AN RTS CLASSIC REFORGED'
+ - var bannerSecondTitle = 'MASSIVE SCALE WARFARE'
+ - var bannerSubTitle = 'MANY COMMANDERS PLAYING RIGHT NOW'
block banner
- mixin banner()
- .mainBannerContainer(class= bannerImage + 'Banner' style='background-image: url(../images/banner/' + bannerImage + '.webp);')
- .bannerContainer.bannerTitle #{bannerFirstTitle}
- .bannerContainer.bannerTitle #{bannerSecondTitle}
- .bannerContainer.bannerSubtitle#playerCounter #{bannerSubTitle}
- block bannerButton
- br
- .bannerButton
- a(href='/play')
- button.landingButton PLAY NOW
-
+ mixin banner
+ .mainBannerContainer(
+ class=bannerImage + 'Banner',
+ style='background-image: url(../images/banner/' + bannerImage + '.webp);'
+ )
+ .bannerContainer.bannerTitle #{ bannerFirstTitle }
+ .bannerContainer.bannerTitle #{ bannerSecondTitle }
+ #playerCounter.bannerContainer.bannerSubtitle #{ bannerSubTitle }
+ block bannerButton
+ br
+ .bannerButton
+ a(href='/play')
+ button.landingButton PLAY NOW
+
block content
-
- //Monkeylord Background
- .mainIntroduction
- .introductionContainer.column12
- h2.highlightText JOIN THOUSANDS
- h1 IN THE BATTLEFIELD
- p Forged Alliance Forever (FAF) is a community-driven project that has continued the development of Supreme Commander: FA since 2011. Bringing this classic RTS to the modern world with well deserved quality of life updates. Ranging from co-op campaigns, matchmaking, smarter AIs, new gamemodes, easy to install mods and maps and up to 16 player in one match. Join one of the biggest RTS communities and deliver absolute warfare!
+ //Monkeylord Background
+ .mainIntroduction
+ .introductionContainer.column12
+ h2.highlightText JOIN THOUSANDS
+ h1 IN THE BATTLEFIELD
+ p Forged Alliance Forever (FAF) is a community-driven project that has continued the development of Supreme Commander: FA since 2011. Bringing this classic RTS to the modern world with well deserved quality of life updates. Ranging from co-op campaigns, matchmaking, smarter AIs, new gamemodes, easy to install mods and maps and up to 16 player in one match. Join one of the biggest RTS communities and deliver absolute warfare!
+
+ .splatForgedBorder
+ .movingBackground1
+ .movingBackground2
+ // The bold white bar that separates containers
- .splatForgedBorder
- .movingBackground1
- .movingBackground2
- // The bold white bar that separates containers
+ .showcaseBackground
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText Better together
+ h1 Co-Op campaign & new missions
+ p Enjoy the complete Supreme Commander:FA campaign with up to four players, a FAF-made Seraphim Campaign with 6 new challenging missions and new standalone hardcore missions for the PVE war veterans that want the AI to absolutely destroy them with the toughest battles.
+ a(href='/campaign-missions')
+ button Campaign overview
+ .showcaseImage.campaignBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.communityBG.column6
+ .showcaseContainer.column6
+ h2.highlightText Thriving community
+ h1 Make new friends or foes
+ p Our diverse playerbase makes sure you have a spot in our community. Whether you want to experience surviving against waves of enemy AI, play custom games, rise in the matchmaking ladder or just have fun with friends, FAF has a space for you.
+ a(href='https://discord.gg/kTsxKu52WU')
+ button Join our Discord
+ a(href='https://forum.faforever.com')
+ button FAF forums
+ .mainShowcase
+ .showcaseContainer.column6
+ h2.highlightText Strategic choices
+ h1 Macro over micro
+ p FAF is a game about making the correct decisions in the fog of battle, adjusting to the ever-changing warfare and chaos, not who can click faster. Your macro skills will matter a lot more to your armies than how well you can micro your individual units.
+ a(href='/tutorials-guides')
+ button How to play
+ .showcaseImage.strategicBG.column6
+ .mainShowcaseReverse
+ .showcaseImage.destructionBG.column6
+ .showcaseContainer.column6
+ h2.highlightText Colossal destruction
+ h1 Conquer every front
+ p Whether you want to crush your opponents in the skies and bomb them back to the stone age, attack with huge land armies or siege your opponents with your battleships. All three theaters of war influence the outcome of battle.
-
-
- .showcaseBackground
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText Better together
- h1 Co-Op campaign & new missions
- p Enjoy the complete Supreme Commander:FA campaign with up to four players, a FAF-made Seraphim Campaign with 6 new challenging missions and new standalone hardcore missions for the PVE war veterans that want the AI to absolutely destroy them with the toughest battles.
- a(href='/campaign-missions')
- button Campaign overview
- .showcaseImage.campaignBG.column6
- .mainShowcaseReverse
- .showcaseImage.communityBG.column6
- .showcaseContainer.column6
- h2.highlightText Thriving community
- h1 Make new friends or foes
- p Our diverse playerbase makes sure you have a spot in our community. Whether you want to experience surviving against waves of enemy AI, play custom games, rise in the matchmaking ladder or just have fun with friends, FAF has a space for you.
- a(href='https://discord.gg/kTsxKu52WU')
- button Join our Discord
- a(href='https://forum.faforever.com')
- button FAF forums
- .mainShowcase
- .showcaseContainer.column6
- h2.highlightText Strategic choices
- h1 Macro over micro
- p FAF is a game about making the correct decisions in the fog of battle, adjusting to the ever-changing warfare and chaos, not who can click faster. Your macro skills will matter a lot more to your armies than how well you can micro your individual units.
- a(href='/tutorials-guides')
- button How to play
- .showcaseImage.strategicBG.column6
- .mainShowcaseReverse
- .showcaseImage.destructionBG.column6
- .showcaseContainer.column6
- h2.highlightText Colossal destruction
- h1 Conquer every front
- p Whether you want to crush your opponents in the skies and bomb them back to the stone age, attack with huge land armies or siege your opponents with your battleships. All three theaters of war influence the outcome of battle.
+ //Factions Start here
+ .splatForgedBorder.transformationReverse
+ .movingBackground1
+ .movingBackground2
- //Factions Start here
+ .factionMainContainer
+ .factionTitle.column12
+ h1.highlightText FOUR FACTIONS
+ h1 OF ABSOLUTE DESTRUCTION
- .splatForgedBorder.transformationReverse
- .movingBackground1
- .movingBackground2
+ .factionContainer.column3.UEF
+ img(src='/../../images/logos/factionuef.svg', alt='')
+ h1 UEF
+ p Chunky mechs and tanks, jets and all the other classic war toys. For those that love vanilla-warfare.
- .factionMainContainer
- .factionTitle.column12
- h1.highlightText FOUR FACTIONS
- h1 OF ABSOLUTE DESTRUCTION
-
- .factionContainer.column3.UEF
- img(src='/../../images/logos/factionuef.svg' alt='')
- h1 UEF
- p Chunky mechs and tanks, jets and all the other classic war toys. For those that love vanilla-warfare.
+ .factionContainer.column3.Cybran
+ img(src='/../../images/logos/factioncybran.svg', alt='')
+ h1 Cybran
+ p Stealthy and fast spiderbots, walking ships and pointy structures. You like being the edgy guy with spikes.
-
- .factionContainer.column3.Cybran
- img(src='/../../images/logos/factioncybran.svg' alt='')
- h1 Cybran
- p Stealthy and fast spiderbots, walking ships and pointy structures. You like being the edgy guy with spikes.
+ .factionContainer.column3.Aeon
+ img(src='/../../images/logos/factionaeon.svg', alt='')
+ h1 Aeon
+ p Floating alien tanks, shields on everything and space donuts. You want to sweat microing your units.
- .factionContainer.column3.Aeon
- img(src='/../../images/logos/factionaeon.svg' alt='')
- h1 Aeon
- p Floating alien tanks, shields on everything and space donuts. You want to sweat microing your units.
+ .factionContainer.column3.Seraphim
+ img(src='/../../images/logos/factionseraphim.svg', alt='')
+ h1 Seraphim
+ p Chicken ACU, chicken assault bots and chicken experimental. You want a balanced army of chickens.
- .factionContainer.column3.Seraphim
- img(src='/../../images/logos/factionseraphim.svg' alt='')
- h1 Seraphim
- p Chicken ACU, chicken assault bots and chicken experimental. You want a balanced army of chickens.
+ //splat end
+ //zoom start
-
-
- //splat end
- //zoom start
-
- //end banner start
+ //end banner start
- .splatForgedBorder
- .movingBackground1
- .movingBackground2
+ .splatForgedBorder
+ .movingBackground1
+ .movingBackground2
- .endBanner
- .bannerContainer.column12
- h2 Ready to Play?
- h1.highlightText EXPERIENCE SUPREME COMMANDER
- h1 THE WAY IT WAS INTENDED
- .bannerContainer.column12
- a(href='/play')
- button PLAY NOW
- a(href='https://discord.gg/mXahVSKGVb')
- button Join our Discord
+ .endBanner
+ .bannerContainer.column12
+ h2 Ready to Play?
+ h1.highlightText EXPERIENCE SUPREME COMMANDER
+ h1 THE WAY IT WAS INTENDED
+ .bannerContainer.column12
+ a(href='/play')
+ button PLAY NOW
+ a(href='https://discord.gg/mXahVSKGVb')
+ button Join our Discord
diff --git a/src/backend/templates/views/leaderboards.pug b/src/backend/templates/views/leaderboards.pug
index 1167486f..58f1ae1e 100644
--- a/src/backend/templates/views/leaderboards.pug
+++ b/src/backend/templates/views/leaderboards.pug
@@ -3,90 +3,100 @@ extends ../layouts/default
block bannerMixin
block content
- .leaderboardBackground
- .mainLeaderboardContainer
- .leaderboardContainer.column12.centerYourself
- p Search for a player in the selected leaderboard
- #errorLog
- input#input(onkeyup=`pressEnter(event)` type='text' placeholder='Player Name')
- #searchbar
- .searchBar
- ul
- #placeMe
- ul#clearSearch.clearButton.appearWhenSearching
- img(src='/../../images/fontAwesomeIcons/trash.svg' alt='')
-
-
+ .leaderboardBackground
+ .mainLeaderboardContainer
+ .leaderboardContainer.column12.centerYourself
+ p Search for a player in the selected leaderboard
+ #errorLog
+ input#input(
+ onkeyup='pressEnter(event)',
+ type='text',
+ placeholder='Player Name'
+ )
+ #searchbar
+ .searchBar
+ ul
+ #placeMe
+ ul#clearSearch.clearButton.appearWhenSearching
+ img(src='/../../images/fontAwesomeIcons/trash.svg', alt='')
-
-
- .newLeaderboard.leaderboardBorder.appearWhenSearching#searchResults.leaderboardNoAwards
- .newLeaderboardContainer.column12.newLeaderboardCategory
- .column1
- h2 Rank
- .column4
- h2 Player
- .column2
- h2 Rating
-
- .column2
- h2 Win Rate
-
- .column3
- h2 Total Games
-
- #insertSearch
-
- .mainLeaderboardContainer.leaderboardCategory
- .column12.leaderboardSelect
- select.leaderboardFilter(onchange='changeLeaderboard(this.value)')
- option(value='1v1') 1v1
- option(value='2v2') 2v2
- option(value='4v4') 4v4
- option(value='global') Global
- select.leaderboardFilter(onchange='timeCheck(this.value)')
- option(value='12') 1 Year
- option(value='6') 6 Months
- option(value='3') 3 Months
- option(value='1') 1 Month
- .categoryContainer
- .categoryButton(onclick= `pageChange(0)`).pageButton.exhaustedButton First
- .categoryButton(onclick= `pageChange(lastPage)`).pageButton Last
- .categoryContainer
- .categoryButton(onclick= `pageChange(pageNumber - 1)`).pageButton.exhaustedButton Previous
- .categoryButton(onclick= `pageChange(pageNumber + 1)`).pageButton Next
- //DO NOT Change the order of these without changing the js. For them to work, they need to be in this specific order
+ #searchResults.newLeaderboard.leaderboardBorder.appearWhenSearching.leaderboardNoAwards
+ .newLeaderboardContainer.column12.newLeaderboardCategory
+ .column1
+ h2 Rank
+ .column4
+ h2 Player
+ .column2
+ h2 Rating
+ .column2
+ h2 Win Rate
- .newLeaderboard#mainLeaderboard
- .newLeaderboardContainer.column12.newLeaderboardCategory
- .column1
- h2 Rank
- .column4
- h2 Player
- .column2.categoryFilter
- h2(onclick='filterLeaderboards(1)') Rating
- //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
- .column2.categoryFilter
- h2(onclick='filterLeaderboards(2)') Win Rate
- //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
- .column3.categoryFilter
- h2(onclick='filterLeaderboards(3)') Total Games
- //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
-
- #insertPlayer
-
-
- .mainLeaderboardContainer.leaderboardCategory
- .column12.leaderboardSelect
+ .column3
+ h2 Total Games
- .categoryContainer
- .categoryButton(onclick= `pageChange(0)`).pageButton.exhaustedButton First
- .categoryButton(onclick= `pageChange(lastPage)`).pageButton Last
- .categoryContainer
- .categoryButton(onclick= `pageChange(pageNumber - 1)`).pageButton.exhaustedButton Previous
- .categoryButton(onclick= `pageChange(pageNumber + 1)`).pageButton Next
+ #insertSearch
+
+ .mainLeaderboardContainer.leaderboardCategory
+ .column12.leaderboardSelect
+ select.leaderboardFilter(
+ onchange='changeLeaderboard(this.value)'
+ )
+ option(value='1v1') 1v1
+ option(value='2v2') 2v2
+ option(value='4v4') 4v4
+ option(value='global') Global
+ select.leaderboardFilter(onchange='timeCheck(this.value)')
+ option(value='12') 1 Year
+ option(value='6') 6 Months
+ option(value='3') 3 Months
+ option(value='1') 1 Month
+ .categoryContainer
+ .categoryButton.pageButton.exhaustedButton(
+ onclick='pageChange(0)'
+ ) First
+ .categoryButton.pageButton(onclick='pageChange(lastPage)') Last
+ .categoryContainer
+ .categoryButton.pageButton.exhaustedButton(
+ onclick='pageChange(pageNumber - 1)'
+ ) Previous
+ .categoryButton.pageButton(
+ onclick='pageChange(pageNumber + 1)'
+ ) Next
+ //DO NOT Change the order of these without changing the js. For them to work, they need to be in this specific order
+
+ #mainLeaderboard.newLeaderboard
+ .newLeaderboardContainer.column12.newLeaderboardCategory
+ .column1
+ h2 Rank
+ .column4
+ h2 Player
+ .column2.categoryFilter
+ h2(onclick='filterLeaderboards(1)') Rating
+ //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
+ .column2.categoryFilter
+ h2(onclick='filterLeaderboards(2)') Win Rate
+ //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
+ .column3.categoryFilter
+ h2(onclick='filterLeaderboards(3)') Total Games
+ //img.categoryCaret(src='/images/fontAwesomeIcons/caret.svg')
+
+ #insertPlayer
+
+ .mainLeaderboardContainer.leaderboardCategory
+ .column12.leaderboardSelect
+ .categoryContainer
+ .categoryButton.pageButton.exhaustedButton(
+ onclick='pageChange(0)'
+ ) First
+ .categoryButton.pageButton(onclick='pageChange(lastPage)') Last
+ .categoryContainer
+ .categoryButton.pageButton.exhaustedButton(
+ onclick='pageChange(pageNumber - 1)'
+ ) Previous
+ .categoryButton.pageButton(
+ onclick='pageChange(pageNumber + 1)'
+ ) Next
block js
- script(src=webpackAssetJS('leaderboards'))
-
+ script(src=webpackAssetJS('leaderboards'))
diff --git a/src/backend/templates/views/markdown.pug b/src/backend/templates/views/markdown.pug
index f2f08cee..f6a8f0b3 100644
--- a/src/backend/templates/views/markdown.pug
+++ b/src/backend/templates/views/markdown.pug
@@ -1,5 +1,5 @@
extends ../layouts/default
block content
- .container
- != content
+ .container
+ != content
diff --git a/src/backend/templates/views/news.pug b/src/backend/templates/views/news.pug
index df2bfa9d..6afc6895 100644
--- a/src/backend/templates/views/news.pug
+++ b/src/backend/templates/views/news.pug
@@ -1,17 +1,21 @@
extends ../layouts/default
block bannerMixin
-
+
block content
- #articleStart
- .articleTopTitle.column12
- h2 Recent
- h1.highlightText News
- #articleMain.column12
- each newsArticle in news
- div(class=['articleContainer', 'column4'])
- div.articleImage(style='background-image:url(' + newsArticle.media + ')' )
- div.articleText
- h2.articleAuthorDate=`By ${newsArticle.author} on ${newsArticle.date.substring(0, 10)}`
- h1.articleTitle=newsArticle.title
- div.articleContent !{newsArticle.content.substring(0, 150)} ...
- button(onClick="window.location.href = '/news/" + newsArticle.slug + "'") Learn More
+ #articleStart
+ .articleTopTitle.column12
+ h2 Recent
+ h1.highlightText News
+ #articleMain.column12
+ each newsArticle in news
+ div(class=['articleContainer', 'column4'])
+ .articleImage(
+ style='background-image:url(' + newsArticle.media + ')'
+ )
+ .articleText
+ h2.articleAuthorDate= `By ${newsArticle.author} on ${newsArticle.date.substring(0, 10)}`
+ h1.articleTitle= newsArticle.title
+ .articleContent !{ newsArticle.content.substring(0, 150) } ...
+ button(
+ onClick="window.location.href = '/news/" + newsArticle.slug + "'"
+ ) Learn More
diff --git a/src/backend/templates/views/newsArticle.pug b/src/backend/templates/views/newsArticle.pug
index f8a586f3..eedc62d9 100644
--- a/src/backend/templates/views/newsArticle.pug
+++ b/src/backend/templates/views/newsArticle.pug
@@ -1,19 +1,21 @@
extends ../layouts/default
block bannerMixin
-
+
block content
- #emptyContainer
- .newsAbsolute
- a(href='/news')
- button Back to News page
- #newsBackground(style='background-image:url(\'/images/black' + Math.floor(Math.random() * 4) + '.jpg\')' )
- #newsMain
- .newsContainer.column12
- h1#title=newsArticle.title
- .newsContainer.column12
- #featuredImage
- img(src=newsArticle.media alt=newsArticle.title)
- .newsContainer.column12
- h1#authorDate=`By ${newsArticle.author} on ${newsArticle.date.substring(0, 10)}`
- .newsContainer.column12
- #content !{newsArticle.content}
+ #emptyContainer
+ .newsAbsolute
+ a(href='/news')
+ button Back to News page
+ #newsBackground(
+ style="background-image:url('/images/black" + Math.floor(Math.random() * 4) + ".jpg')"
+ )
+ #newsMain
+ .newsContainer.column12
+ h1#title= newsArticle.title
+ .newsContainer.column12
+ #featuredImage
+ img(src=newsArticle.media, alt=newsArticle.title)
+ .newsContainer.column12
+ h1#authorDate= `By ${newsArticle.author} on ${newsArticle.date.substring(0, 10)}`
+ .newsContainer.column12
+ #content !{ newsArticle.content }
diff --git a/src/backend/templates/views/newshub.pug b/src/backend/templates/views/newshub.pug
index 05b53062..6371a64b 100644
--- a/src/backend/templates/views/newshub.pug
+++ b/src/backend/templates/views/newshub.pug
@@ -1,83 +1,123 @@
doctype html
html(lang='en')
- //- HTML HEADER
- head
- meta(charset="utf-8")
- meta(name="viewport", content="width=device-width, initial-scale=1.0")
- meta(http-equiv="X-UA-Compatible" content="IE=edge")
- meta(name="description" content="FAF Community Website")
+ //- HTML HEADER
+ head
+ meta(charset='utf-8')
+ meta(name='viewport', content='width=device-width, initial-scale=1.0')
+ meta(http-equiv='X-UA-Compatible', content='IE=edge')
+ meta(name='description', content='FAF Community Website')
- title= title || 'Forged Alliance Forever'
- link(rel="shortcut icon", href="/images/favicon.png", type="image/png")
+ title= title || 'Forged Alliance Forever'
+ link(rel='shortcut icon', href='/images/favicon.png', type='image/png')
- // Fonts
- link(href='https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz' rel='stylesheet' type='text/css')
- link(href='https://fonts.googleapis.com/css2?family=Electrolize&display=swap' rel='stylesheet')
+ // Fonts
+ link(
+ href='https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz',
+ rel='stylesheet',
+ type='text/css'
+ )
+ link(
+ href='https://fonts.googleapis.com/css2?family=Electrolize&display=swap',
+ rel='stylesheet'
+ )
- link(href='https://fonts.googleapis.com/css2?family=Electrolize&family=Russo+One&display=swap' rel='stylesheet' type='text/css')
- link(rel='stylesheet' href='https://fonts.googleapis.com/css2?family=Orbitron&display=swap')
+ link(
+ href='https://fonts.googleapis.com/css2?family=Electrolize&family=Russo+One&display=swap',
+ rel='stylesheet',
+ type='text/css'
+ )
+ link(
+ rel='stylesheet',
+ href='https://fonts.googleapis.com/css2?family=Orbitron&display=swap'
+ )
- //- Customise the stylesheet for your site by editing /public/styles/site.sass
- link(href="/styles/css/site.min.css?version="+Date.now(), rel="stylesheet")
+ //- Customise the stylesheet for your site by editing /public/styles/site.sass
+ link(
+ href='/styles/css/site.min.css?version=' + Date.now(),
+ rel='stylesheet'
+ )
body
+ .clientBackground
+ .clientMainFeature
+ .featureSubGrid.column3.featureLink
+ .featureSocial.column12
+ .featureLink.featureDiscord.column4
+ a(
+ target='_blank',
+ href='https://discord.gg/mXahVSKGVb'
+ )
+ img(src='/../../images/logos/discord.svg', alt='')
+ .featureLink.featureTwitch.column4
+ a(
+ target='_blank',
+ href='https://www.twitch.tv/directory/game/Supreme%20Commander%3A%20Forged%20Alliance'
+ )
+ img(src='/../../images/logos/twitch.png', alt='')
+ .featureLink.featureYoutube.column4
+ a(
+ target='_blank',
+ href='https://www.youtube.com/channel/UChiCO27pbaXDR0d0JfxlIpw'
+ )
+ img(src='/../../images/logos/youtube.png', alt='')
+ .clientSupportFAF.column12
+ a(target='_blank', href='https://www.patreon.com/faf')
+ h1 SUPPORT FAF
+ img(
+ src='/../../images/logos/patreon.png',
+ alt=''
+ )
+ .clientSupportFAF.column12
+ // TODO Jip needs to make a link or find one for the report a bug button
+ a(
+ target='_blank',
+ href='https://discord.com/channels/197033481883222026/907037442832482315'
+ )
+ h2 Report a Bug
+ #clientMain
+ #clientSpawn
+ .clientContainer.column1
+ a(
+ target='_blank',
+ href='https://discord.com/channels/197033481883222026/1102940262617067541'
+ )
+ .clientNewsRequest
+ h1.column12 Submit a news item
- .clientBackground
- .clientMainFeature
- .featureSubGrid.column3.featureLink
- .featureSocial.column12
- .featureLink.featureDiscord.column4
- a(target='_blank' href='https://discord.gg/mXahVSKGVb')
- img(src='/../../images/logos/discord.svg' alt='')
- .featureLink.featureTwitch.column4
- a(target='_blank' href='https://www.twitch.tv/directory/game/Supreme%20Commander%3A%20Forged%20Alliance')
- img(src='/../../images/logos/twitch.png' alt='')
- .featureLink.featureYoutube.column4
- a(target='_blank' href='https://www.youtube.com/channel/UChiCO27pbaXDR0d0JfxlIpw')
- img(src='/../../images/logos/youtube.png' alt='')
- .clientSupportFAF.column12
- a(target='_blank' href='https://www.patreon.com/faf')
- h1 SUPPORT FAF
- img(src='/../../images/logos/patreon.png' alt='')
- .clientSupportFAF.column12
- // TODO Jip needs to make a link or find one for the report a bug button
- a(target='_blank' href='https://discord.com/channels/197033481883222026/907037442832482315')
- h2 Report a Bug
-
-
-
- #clientMain
- #clientSpawn
-
- .clientContainer.column1
- a(target='_blank' href='https://discord.com/channels/197033481883222026/1102940262617067541')
- .clientNewsRequest
-
- h1.column12 Submit a news item
-
- #clientArrowLeft
- .fas.fa-chevron-left
- #clientArrowRigth
- .fas.fa-chevron-right
- .clientMenu
- .clientMenuContainer.column2
- ul NEW PLAYERS
- a(target='_blank' href='https://forum.faforever.com/category/18/frequently-asked-questions') FAQ
- a(target='_blank' href='https://youtu.be/Nks9loE96ok') First Time in FAF
- a(target='_blank' href='https://wiki.faforever.com/en/Play/Learning/Learning-SupCom') Community Guides
- .clientMenuContainer.column2
- ul COMMUNITY
- a(target='_blank' href='https://forum.faforever.com/') Forums
- a(target='_blank' href='https://kazbek.github.io/FAF-Analytics/') Player Statistics
- a(target='_blank' href='https://www.faforever.com/coc') Player Guidelines
- .clientMenuContainer.column2
- ul DEVELOPMENT
- a(target='_blank' href='https://github.com/FAForever/fa') Github
- a(target='_blank' href='https://faforever.com/contribution') Contribute
- a(target='_blank' href='https://wiki.faforever.com/en/Vault-Rules') Vault Rules
- .clientMenuContainer.clientTournament.column6
- #tournamentSpawn
- ul.column12 UPCOMING TOURNAMENTS/EVENTS
-
+ #clientArrowLeft
+ .fas.fa-chevron-left
+ #clientArrowRigth
+ .fas.fa-chevron-right
+ .clientMenu
+ .clientMenuContainer.column2
+ ul NEW PLAYERS
+ a(
+ target='_blank',
+ href='https://forum.faforever.com/category/18/frequently-asked-questions'
+ ) FAQ
+ a(target='_blank', href='https://youtu.be/Nks9loE96ok') First Time in FAF
+ a(
+ target='_blank',
+ href='https://wiki.faforever.com/en/Play/Learning/Learning-SupCom'
+ ) Community Guides
+ .clientMenuContainer.column2
+ ul COMMUNITY
+ a(target='_blank', href='https://forum.faforever.com/') Forums
+ a(
+ target='_blank',
+ href='https://kazbek.github.io/FAF-Analytics/'
+ ) Player Statistics
+ a(target='_blank', href='https://www.faforever.com/coc') Player Guidelines
+ .clientMenuContainer.column2
+ ul DEVELOPMENT
+ a(target='_blank', href='https://github.com/FAForever/fa') Github
+ a(target='_blank', href='https://faforever.com/contribution') Contribute
+ a(
+ target='_blank',
+ href='https://wiki.faforever.com/en/Vault-Rules'
+ ) Vault Rules
+ .clientMenuContainer.clientTournament.column6
+ #tournamentSpawn
+ ul.column12 UPCOMING TOURNAMENTS/EVENTS
script(src=webpackAssetJS('newshub'))
diff --git a/src/backend/templates/views/play.pug b/src/backend/templates/views/play.pug
index c1b389d2..969f999b 100644
--- a/src/backend/templates/views/play.pug
+++ b/src/backend/templates/views/play.pug
@@ -3,100 +3,98 @@ extends ../layouts/default
block bannerMixin
block content
- .playMain
-
- h1 Play the best RTS out there
- h2.highlightText Join hundreds of players on the battlefield
+ .playMain
+ h1 Play the best RTS out there
+ h2.highlightText Join hundreds of players on the battlefield
-
-
- br
+ br
- .playInnerGrid
- .playContainer
- h1 Before installing FAF
- p Doing these now will ensure an easy installation later and avoid issues.
-
- .playCheckboxContainer
- input(type='checkbox')
- label Buy and install Supreme Commander:Forged Alliance (SC:FA) via Steam or GOG.
- br
- a(href='https://store.steampowered.com/app/9420/Supreme_Commander_Forged_Alliance' target='_blank')
- button Buy on Steam
- a(href='https://www.gog.com/en/game/supreme_commander_gold_edition' target='_blank')
- button Buy on GOG
- br
- input(type='checkbox')
- label Create and activate your FAF account
- br
- a(href='/account/register' target='_blank')
- button Register
-
-
- br
-
- input(type='checkbox')
- label Link Steam or GOG account to your FAF account.
- br
- a(href='/account/link' target='_blank')
- button Link Steam Account
- a(href='/account/linkGog' target='_blank')
- button Link GOG Account
- br
- input(type='checkbox')
- label Run SC:FA locally once to create a profile in it (game generates necessary files doing so)
- br
-
- h2 Why do I need to link my Steam/GOG account to FAF?
- p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
-
-
- h2.highlightText Checked everything above?
- h2.highlightText Then follow these easy steps to play FAF
-
-
+ .playInnerGrid
+ .playContainer
+ h1 Before installing FAF
+ p Doing these now will ensure an easy installation later and avoid issues.
-
- .playContainer
- h1 1 - Download and install FAF
- p Download the FAF Client and install it. FAF is open source and safe to use.
If your Windows computer stops you from running FAF, click on "More info" and you'll be able to run it.
This happens because FAF doesn't pay Microsoft for security certifications. So it's an "unrecognized app" for Windows.
If you have any issues or troubleshooting, join our Discord or use our forum for help.
- a(href="#" id="faf-client-download")
- button Download FAF
- br
- br
- a(href='https://discord.gg/mXahVSKGVb')
- button Discord
- a(href='https://forum.faforever.com/')
- button Forum
- a(href='https://wiki.faforever.com/en/Play/Linux-Install')
- button Linux Installation
- br
- br
- img(src='/images/windowsDefender.png')
-
- .playContainer
- h1 2 - Run FAF and find your SC:FA directory
- p Run FAF and log in with the same credentials you used for your FAF account.
Now once you try joining a lobby or creating a game in the Play tab, FAF will ask for your SC:FA game files/directory.
Below are instructions if you dont know where you installed SC:FA on Steam or GOG
- a(href='https://www.youtube.com/watch?v=-BVEctqzkxw')
- button How to find Steam directory
- a(href='https://www.youtube.com/watch?v=7IzJlw3Tdtg')
- button How to find GOG directory
-
-
-
-
-
-
- .playContainer
- h1 3 - Enjoy Forged Alliance Forever!
- p You are officially done! If you are new to the game, we recommend
watching the video below and joining the official FAF Discord,
you'll find plenty of friendly people to play, ask questions and become part of the community!
-
- a(href='https://discord.gg/kTsxKu52WU')
- button Join the FAF Discord
- br
- br
- iframe(style="width:75%; height:45vh;" src="https://www.youtube.com/embed/Nks9loE96ok" title="NEW TO FAF? || SUPREME COMMANDER TUTORIAL", allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;" allowfullscreen )
+ .playCheckboxContainer
+ input(type='checkbox')
+ label Buy and install Supreme Commander:Forged Alliance (SC:FA) via Steam or GOG.
+ br
+ a(
+ href='https://store.steampowered.com/app/9420/Supreme_Commander_Forged_Alliance',
+ target='_blank'
+ )
+ button Buy on Steam
+ a(
+ href='https://www.gog.com/en/game/supreme_commander_gold_edition',
+ target='_blank'
+ )
+ button Buy on GOG
+ br
+ input(type='checkbox')
+ label Create and activate your FAF account
+ br
+ a(href='/account/register', target='_blank')
+ button Register
+
+ br
+
+ input(type='checkbox')
+ label Link Steam or GOG account to your FAF account.
+ br
+ a(href='/account/link', target='_blank')
+ button Link Steam Account
+ a(href='/account/linkGog', target='_blank')
+ button Link GOG Account
+ br
+ input(type='checkbox')
+ label Run SC:FA locally once to create a profile in it (game generates necessary files doing so)
+ br
+
+ h2 Why do I need to link my Steam/GOG account to FAF?
+ p FAF as an organization doesn't own the copyright or trademark to SC:FA (Square Enix does).
Therefore, we need to verify you own a copy of SC:FA to prevent piracy.
+
+ h2.highlightText Checked everything above?
+ h2.highlightText Then follow these easy steps to play FAF
+
+ .playContainer
+ h1 1 - Download and install FAF
+ p Download the FAF Client and install it. FAF is open source and safe to use.
If your Windows computer stops you from running FAF, click on "More info" and you'll be able to run it.
This happens because FAF doesn't pay Microsoft for security certifications. So it's an "unrecognized app" for Windows.
If you have any issues or troubleshooting, join our Discord or use our forum for help.
+ a#faf-client-download(href='#')
+ button Download FAF
+ br
+ br
+ a(href='https://discord.gg/mXahVSKGVb')
+ button Discord
+ a(href='https://forum.faforever.com/')
+ button Forum
+ a(href='https://wiki.faforever.com/en/Play/Linux-Install')
+ button Linux Installation
+ br
+ br
+ img(src='/images/windowsDefender.png')
+
+ .playContainer
+ h1 2 - Run FAF and find your SC:FA directory
+ p Run FAF and log in with the same credentials you used for your FAF account.
Now once you try joining a lobby or creating a game in the Play tab, FAF will ask for your SC:FA game files/directory.
Below are instructions if you dont know where you installed SC:FA on Steam or GOG
+ a(href='https://www.youtube.com/watch?v=-BVEctqzkxw')
+ button How to find Steam directory
+ a(href='https://www.youtube.com/watch?v=7IzJlw3Tdtg')
+ button How to find GOG directory
+
+ .playContainer
+ h1 3 - Enjoy Forged Alliance Forever!
+ p You are officially done! If you are new to the game, we recommend
watching the video below and joining the official FAF Discord,
you'll find plenty of friendly people to play, ask questions and become part of the community!
+
+ a(href='https://discord.gg/kTsxKu52WU')
+ button Join the FAF Discord
+ br
+ br
+ iframe(
+ style='width: 75%; height: 45vh',
+ src='https://www.youtube.com/embed/Nks9loE96ok',
+ title='NEW TO FAF? || SUPREME COMMANDER TUTORIAL',
+ allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;',
+ allowfullscreen
+ )
block js
- script(src=webpackAssetJS('play'))
-
+ script(src=webpackAssetJS('play'))
diff --git a/src/backend/templates/views/scfa-vs-faf.pug b/src/backend/templates/views/scfa-vs-faf.pug
index 20751d2b..62955f8e 100644
--- a/src/backend/templates/views/scfa-vs-faf.pug
+++ b/src/backend/templates/views/scfa-vs-faf.pug
@@ -1,65 +1,68 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "scfa"
- - var bannerFirstTitle = "CONTINUING THE DEVELOPMENT"
- - var bannerSecondTitle = "OF SC:FA, FOREVER"
- - var bannerSubTitle = "MAINTAINING THE BEST FEATURES AND REMOVING THE WORST ISSUES"
+ - var bannerImage = 'scfa'
+ - var bannerFirstTitle = 'CONTINUING THE DEVELOPMENT'
+ - var bannerSecondTitle = 'OF SC:FA, FOREVER'
+ - var bannerSubTitle = 'MAINTAINING THE BEST FEATURES AND REMOVING THE WORST ISSUES'
block content
- .descriptionMain
- .descriptionContainer
- h2 Significant Changes in FAF
- p In this page you will find the biggest and most signficant changes done in FAF. From certain mods being part of the game (such as Engymod), adding additional hotkey layouts and more customization, allowing 16 player matches, to all the small unit value tweaks (such as reducing AA damage from Restorers). If you want to learn all the details, you can read more in the FAF wiki.
- a(href='https://wiki.faforever.com/en/FAQ/Changes-from-steam' target='_blank')
- button Wiki Steam Changes Page
- .changeMain
- .changeSubGrid.column4
- .changeContainer.column12
- h1 Support Factories
- p Instead of having to assist only one T2 or T3 factory. Now mass production of T2-T3 armies is possible with support factories. Now your first T2 or T3 factory will be a "HQ" factory, unlocking the new units which can also be built in support factories (cheaper T2-T3 factories that can only build units of that tier as long as the HQ is alive).
-
- .changeSubGrid.column4
- .changeContainer.column12
- h1 More Hotkeys/Hotbuild
- p Hotbuild is a mod which lets you quickly queue buildings and units via hotkeys instead of having to click the build menu. It does this by having multiple structures/units bound to the same key, which you press multiple times to cycle through.
- .changeSubGrid.column4
- .changeContainer.column12
- h1 Lobby Changes
- li Maximum player count was increased to 16. In addition to unit categories,
- li it's possible to restrict specific units. If you hate mercies or have a grudge towards UEF T3 gunships, you have the power to disable specifically them.
-
- li Improved AI known as Sorian AI has been added.
- li Option to auto-balance teams based on rating.
- .changeSubGrid.column4
- .changeContainer.column12
- h1 Overcharge
- li Starting energy storage was reduced to 4000. An energy storage has to be built before overcharge can be used.
- li Overcharge was tweaked to require more energy in order to deal more damage. This makes T3 units more effective versus an ACU, since more energy is required to kill them.
- li An auto-overcharge was added. When enabled, ACUs will automatically overcharge best targets when possible.
- .changeSubGrid.column4
- .changeContainer.column12
- h1 Veterancy
- p Original veterancy system counted number of kills, making high tier units vet very quickly from killing many T1 units. FAF replaced this system with mass-based veterancy, where most units have to kill 200% of their mass cost in order to gain a veterancy level. Veterancy is now also shared by all units that damaged the dying unit, proportionally to damage dealt.
- .changeSubGrid.column4
- .changeContainer.column12
- h1 Miscellaneous Changes
- li Naval units leave underwater wreckage (now wrecks can be reclaimed after a navy fight for their mass).
- li Unit groups can be given special targetting priorities, such as engineers first, economy units first, ACU first, and so on. Available via a UI mod called Target Priorities.
- .changeSubGrid.column12
- .changeContainer.column12
- h1 Balance Changes
- li Most of the atrociously unbalanced units were fixed (such as Restorer AA capacity or MML homing on targets)
- li T1 transports can no longer transport ACUs. This insane change was introduced in the Steam version.
- li T1 air scouts are much cheaper. They also have radar and sonar.
- li T1 bombers were tweaked to remove hoverbombing. In exchange, T1 bombers were buffed.
- li ACU upgrades were rebalanced across the board.
- li Support ACUs were rebalanced to be more useful in the field.
- li ACU explosion damage was reduced to 2500 to reduce the number of draws.
- li Mobile T3 AA units were added to each faction. This makes late game experimental pushes less susceptible to enemy air.
- li Static T3 anti-air is much cheaper.
- li Air staging stations can now be built by T1 engineers.
- .descriptionMain
- .descriptionContainer
- h2 Patch Notes
- p If you want to find out all the small tweaks done to units across the years. Such as changing the speed of hover units from 4.3 to 3.0, or the turning rate of certain weapons. Below you will find a link to the patchnotes starting from 2015.
- a(href='http://patchnotes.faforever.com/' target='_blank')
- button FAF Patch Notes
+ .descriptionMain
+ .descriptionContainer
+ h2 Significant Changes in FAF
+ p In this page you will find the biggest and most signficant changes done in FAF. From certain mods being part of the game (such as Engymod), adding additional hotkey layouts and more customization, allowing 16 player matches, to all the small unit value tweaks (such as reducing AA damage from Restorers). If you want to learn all the details, you can read more in the FAF wiki.
+ a(
+ href='https://wiki.faforever.com/en/FAQ/Changes-from-steam',
+ target='_blank'
+ )
+ button Wiki Steam Changes Page
+ .changeMain
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 Support Factories
+ p Instead of having to assist only one T2 or T3 factory. Now mass production of T2-T3 armies is possible with support factories. Now your first T2 or T3 factory will be a "HQ" factory, unlocking the new units which can also be built in support factories (cheaper T2-T3 factories that can only build units of that tier as long as the HQ is alive).
+
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 More Hotkeys/Hotbuild
+ p Hotbuild is a mod which lets you quickly queue buildings and units via hotkeys instead of having to click the build menu. It does this by having multiple structures/units bound to the same key, which you press multiple times to cycle through.
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 Lobby Changes
+ li Maximum player count was increased to 16. In addition to unit categories,
+ li it's possible to restrict specific units. If you hate mercies or have a grudge towards UEF T3 gunships, you have the power to disable specifically them.
+
+ li Improved AI known as Sorian AI has been added.
+ li Option to auto-balance teams based on rating.
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 Overcharge
+ li Starting energy storage was reduced to 4000. An energy storage has to be built before overcharge can be used.
+ li Overcharge was tweaked to require more energy in order to deal more damage. This makes T3 units more effective versus an ACU, since more energy is required to kill them.
+ li An auto-overcharge was added. When enabled, ACUs will automatically overcharge best targets when possible.
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 Veterancy
+ p Original veterancy system counted number of kills, making high tier units vet very quickly from killing many T1 units. FAF replaced this system with mass-based veterancy, where most units have to kill 200% of their mass cost in order to gain a veterancy level. Veterancy is now also shared by all units that damaged the dying unit, proportionally to damage dealt.
+ .changeSubGrid.column4
+ .changeContainer.column12
+ h1 Miscellaneous Changes
+ li Naval units leave underwater wreckage (now wrecks can be reclaimed after a navy fight for their mass).
+ li Unit groups can be given special targetting priorities, such as engineers first, economy units first, ACU first, and so on. Available via a UI mod called Target Priorities.
+ .changeSubGrid.column12
+ .changeContainer.column12
+ h1 Balance Changes
+ li Most of the atrociously unbalanced units were fixed (such as Restorer AA capacity or MML homing on targets)
+ li T1 transports can no longer transport ACUs. This insane change was introduced in the Steam version.
+ li T1 air scouts are much cheaper. They also have radar and sonar.
+ li T1 bombers were tweaked to remove hoverbombing. In exchange, T1 bombers were buffed.
+ li ACU upgrades were rebalanced across the board.
+ li Support ACUs were rebalanced to be more useful in the field.
+ li ACU explosion damage was reduced to 2500 to reduce the number of draws.
+ li Mobile T3 AA units were added to each faction. This makes late game experimental pushes less susceptible to enemy air.
+ li Static T3 anti-air is much cheaper.
+ li Air staging stations can now be built by T1 engineers.
+ .descriptionMain
+ .descriptionContainer
+ h2 Patch Notes
+ p If you want to find out all the small tweaks done to units across the years. Such as changing the speed of hover units from 4.3 to 3.0, or the turning rate of certain weapons. Below you will find a link to the patchnotes starting from 2015.
+ a(href='http://patchnotes.faforever.com/', target='_blank')
+ button FAF Patch Notes
diff --git a/src/backend/templates/views/tutorials-guides.pug b/src/backend/templates/views/tutorials-guides.pug
index ca9e7dd0..54f7eb16 100644
--- a/src/backend/templates/views/tutorials-guides.pug
+++ b/src/backend/templates/views/tutorials-guides.pug
@@ -1,52 +1,70 @@
extends ../layouts/default
block bannerData
- - var bannerImage = "tutorial"
- - var bannerFirstTitle = "TIPS AND TRICKS TO"
- - var bannerSecondTitle = "SURPASS YOUR OPPONENTS"
- - var bannerSubTitle = "THEY'LL NEVER KNOW WHAT HIT THEM"
+ - var bannerImage = 'tutorial'
+ - var bannerFirstTitle = 'TIPS AND TRICKS TO'
+ - var bannerSecondTitle = 'SURPASS YOUR OPPONENTS'
+ - var bannerSubTitle = "THEY'LL NEVER KNOW WHAT HIT THEM"
block content
- .descriptionMain
- .descriptionContainer
- h2 Learn the Basics
- p Albeit FAF isn't a very demanding game when it comes to inputs like a fighting game for example, it does require to know some basic fundamentals. However, besides having written guides and video tutorials to learn, FAF also has contributors whose whole mission is training new players.
- .tutorialMain
- .tutorialTop.column6
- h2 Video Guides
- .tutorialTop.column6
- h2 Written Guides
- .tutorialContainer.column6
- a(href='https://youtu.be/Nks9loE96ok' target='_blank')
- h1 FAF Basics
- .tutorialContainer.column6
- a(href='https://docs.google.com/document/d/13S4nBDfcBK4WmFtykXGKNmvIPe9L2nbiriISpHNgE4U/edit' target='_blank')
- h1 Core Fundamentals
- .tutorialContainer.column6
- a(href='https://www.youtube.com/watch?v=U-rD4fpdmNk' target='_blank')
- h1 Basic Build Orders
- .tutorialContainer.column6
- a(href='https://forum.faforever.com/topic/1222/how-to-improve-forever-6-laws?_=1625166213365' target='_blank')
- h1 Blackheart's 6 Laws
- .tutorialContainer.column6
- a(href='https://www.youtube.com/watch?v=eHNmuVf9IvE' target='_blank')
- h1 How to Manage Eco
- .tutorialContainer.column6
- a(href='https://forum.faforever.com/topic/766/ladder-1v1-beginner-intermediate-and-advanced-topics-by-arma473' target='_blank')
- h1 1v1 Guide
- .tutorialContainer.column12
- a(href='https://wiki.faforever.com/en/Play/Learning/Learning-SupCom' target='_blank')
- h1 ALL FAF GUIDES
-
- .descriptionMain
- .descriptionContainer
- h2 Get a Personal Trainer
- p If you're confused, or stuck in a rut, or you want help from others, you could benefit from some trainers' advice. Through replay analysis, most trainers will be able to find your issues and give you personal advice on how to improve. Trainers are on the FAF official discord and offer help in the #gameplay-and-training channel. The button below will send you there!
- br
- a(href='https://discord.com/channels/197033481883222026/408556023180296193' target='_blank')
- button #gameplay-and-training
-
+ .descriptionMain
+ .descriptionContainer
+ h2 Learn the Basics
+ p Albeit FAF isn't a very demanding game when it comes to inputs like a fighting game for example, it does require to know some basic fundamentals. However, besides having written guides and video tutorials to learn, FAF also has contributors whose whole mission is training new players.
+ .tutorialMain
+ .tutorialTop.column6
+ h2 Video Guides
+ .tutorialTop.column6
+ h2 Written Guides
+ .tutorialContainer.column6
+ a(href='https://youtu.be/Nks9loE96ok', target='_blank')
+ h1 FAF Basics
+ .tutorialContainer.column6
+ a(
+ href='https://docs.google.com/document/d/13S4nBDfcBK4WmFtykXGKNmvIPe9L2nbiriISpHNgE4U/edit',
+ target='_blank'
+ )
+ h1 Core Fundamentals
+ .tutorialContainer.column6
+ a(
+ href='https://www.youtube.com/watch?v=U-rD4fpdmNk',
+ target='_blank'
+ )
+ h1 Basic Build Orders
+ .tutorialContainer.column6
+ a(
+ href='https://forum.faforever.com/topic/1222/how-to-improve-forever-6-laws?_=1625166213365',
+ target='_blank'
+ )
+ h1 Blackheart's 6 Laws
+ .tutorialContainer.column6
+ a(
+ href='https://www.youtube.com/watch?v=eHNmuVf9IvE',
+ target='_blank'
+ )
+ h1 How to Manage Eco
+ .tutorialContainer.column6
+ a(
+ href='https://forum.faforever.com/topic/766/ladder-1v1-beginner-intermediate-and-advanced-topics-by-arma473',
+ target='_blank'
+ )
+ h1 1v1 Guide
+ .tutorialContainer.column12
+ a(
+ href='https://wiki.faforever.com/en/Play/Learning/Learning-SupCom',
+ target='_blank'
+ )
+ h1 ALL FAF GUIDES
+ .descriptionMain
+ .descriptionContainer
+ h2 Get a Personal Trainer
+ p If you're confused, or stuck in a rut, or you want help from others, you could benefit from some trainers' advice. Through replay analysis, most trainers will be able to find your issues and give you personal advice on how to improve. Trainers are on the FAF official discord and offer help in the #gameplay-and-training channel. The button below will send you there!
+ br
+ a(
+ href='https://discord.com/channels/197033481883222026/408556023180296193',
+ target='_blank'
+ )
+ button #gameplay-and-training
- //https://docs.google.com/document/d/13S4nBDfcBK4WmFtykXGKNmvIPe9L2nbiriISpHNgE4U/edit
+ //https://docs.google.com/document/d/13S4nBDfcBK4WmFtykXGKNmvIPe9L2nbiriISpHNgE4U/edit
block js
-
diff --git a/src/frontend/js/.eslintrc b/src/frontend/js/.eslintrc
deleted file mode 100644
index 370855eb..00000000
--- a/src/frontend/js/.eslintrc
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "env": {
- "browser": true,
- "es2020": true
- }
-}
\ No newline at end of file
diff --git a/src/frontend/js/entrypoint/clan-invite.js b/src/frontend/js/entrypoint/clan-invite.js
index 29c36e48..a1c91345 100644
--- a/src/frontend/js/entrypoint/clan-invite.js
+++ b/src/frontend/js/entrypoint/clan-invite.js
@@ -1,7 +1,7 @@
import Awesomplete from 'awesomplete'
import axios from 'axios'
-async function getPlayers () {
+async function getPlayers() {
const response = await axios.get('/data/recent-players.json')
if (response.status !== 200) {
throw new Error('issues getting data')
@@ -11,17 +11,20 @@ async function getPlayers () {
}
getPlayers().then((memberList) => {
- addAwesompleteListener(document.getElementById('invited_player'), memberList)
+ addAwesompleteListener(
+ document.getElementById('invited_player'),
+ memberList
+ )
})
-function addAwesompleteListener (element, memberList) {
+function addAwesompleteListener(element, memberList) {
const list = memberList.map((player) => {
return player.name
})
/* eslint-disable no-new */
new Awesomplete(element, {
- list
+ list,
})
}
@@ -29,7 +32,12 @@ const invitationLinkButton = document.getElementById('invitationLink')
if (invitationLinkButton) {
invitationLinkButton.addEventListener('click', async function (event) {
try {
- await navigator.clipboard.writeText(location.protocol + '//' + location.host + invitationLinkButton.dataset.href)
+ await navigator.clipboard.writeText(
+ location.protocol +
+ '//' +
+ location.host +
+ invitationLinkButton.dataset.href
+ )
invitationLinkButton.innerText = 'copied!'
} catch (err) {
console.error('Failed to copy: ', err)
diff --git a/src/frontend/js/entrypoint/clans.js b/src/frontend/js/entrypoint/clans.js
index 641e29fd..b2cc76cf 100644
--- a/src/frontend/js/entrypoint/clans.js
+++ b/src/frontend/js/entrypoint/clans.js
@@ -2,9 +2,14 @@ import { DataTable } from 'simple-datatables'
import axios from 'axios'
import 'simple-datatables/dist/style.css'
-axios.get('/data/clans.json')
- .then(response => {
- if (response.status !== 200 || !response.data || !response.data.length) {
+axios
+ .get('/data/clans.json')
+ .then((response) => {
+ if (
+ response.status !== 200 ||
+ !response.data ||
+ !response.data.length
+ ) {
console.error('request clans failed')
return
@@ -14,16 +19,16 @@ axios.get('/data/clans.json')
const datatable = new DataTable('#clan-table', {
perPageSelect: null,
data: {
- headings: [
- 'TAG',
- 'NAME',
- 'LEADER',
- 'POPULATION'
- ],
- data: clans.map(item => {
- return [item.tag, item.name, item.leaderName, item.population]
- })
- }
+ headings: ['TAG', 'NAME', 'LEADER', 'POPULATION'],
+ data: clans.map((item) => {
+ return [
+ item.tag,
+ item.name,
+ item.leaderName,
+ item.population,
+ ]
+ }),
+ },
})
datatable.on('datatable.selectrow', (rowIndex, event) => {
diff --git a/src/frontend/js/entrypoint/content-creators.js b/src/frontend/js/entrypoint/content-creators.js
index fcb4d4c2..e67fb8da 100644
--- a/src/frontend/js/entrypoint/content-creators.js
+++ b/src/frontend/js/entrypoint/content-creators.js
@@ -1,4 +1,4 @@
-async function getWordpress () {
+async function getWordpress() {
const response = await fetch('/data/content-creators.json')
const data = await response.json()
const insertWordpress = document.getElementById('contentCreatorWordpress')
diff --git a/src/frontend/js/entrypoint/donation.js b/src/frontend/js/entrypoint/donation.js
index c71e9f5a..181d81b7 100644
--- a/src/frontend/js/entrypoint/donation.js
+++ b/src/frontend/js/entrypoint/donation.js
@@ -6,28 +6,27 @@ Highcharts.chart('container', {
plotBorderWidth: null,
plotShadow: true,
backgroundColor: 'transparent',
- type: 'pie'
-
+ type: 'pie',
},
credits: {
- enabled: false
+ enabled: false,
},
exporting: {
- enabled: false
+ enabled: false,
},
title: {
text: 'How FAF Uses Donations',
style: {
color: '#ffffff',
fontSize: '30px',
- fontFamily: 'electrolize'
- }
+ fontFamily: 'electrolize',
+ },
},
tooltip: {
- pointFormat: '{series.name}: {point.percentage:.1f}%'
+ pointFormat: '{series.name}: {point.percentage:.1f}%',
},
accessibility: {
- enabled: false
+ enabled: false,
},
plotOptions: {
pie: {
@@ -41,33 +40,37 @@ Highcharts.chart('container', {
format: '