├── .github └── CODEOWNERS ├── images ├── clone.png ├── boarder.png ├── favicon.ico ├── sap_18.png ├── bas_clone.png ├── cs_create.png ├── cs_npm_run.png ├── cs_ready.png ├── npm_start.png ├── base_npm_run.png ├── node_v_check.png ├── npm_install.png ├── npm_run_dev.png ├── bas_dev_space.png ├── bas_npm_install.png ├── bas_npm_run_dev.png ├── cs_npm_install.png └── cs_npm_run_dev.png ├── index.js ├── .vscode └── settings.json ├── app ├── profilePic │ ├── profilePic │ │ ├── view │ │ │ ├── BusyDialog.fragment.xml │ │ │ └── App.view.xml │ │ ├── model │ │ │ └── models.js │ │ ├── Component.js │ │ ├── manifest.json │ │ └── controller │ │ │ ├── App.controller.js │ │ │ └── BaseController.js │ └── index.html └── appconfig │ └── fioriSandboxConfig.json ├── CHANGELOG.md ├── _i18n └── messages.properties ├── tsconfig.json ├── server ├── express.js ├── expressSecurity.js ├── healthCheck.js └── overloadProtection.js ├── routes ├── frontend.js ├── index.js └── profilePic.js ├── .devcontainer ├── Dockerfile └── .devcontainer ├── utils ├── texts.js ├── loader.js └── svgRender.js ├── package.json ├── .reuse └── dep5 ├── express-server.js ├── .gitignore ├── README.md ├── LICENSES └── Apache-2.0.txt └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jung-thomas 2 | -------------------------------------------------------------------------------- /images/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/clone.png -------------------------------------------------------------------------------- /images/boarder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/boarder.png -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/favicon.ico -------------------------------------------------------------------------------- /images/sap_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/sap_18.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import ExpressServer from './express-server.js' 2 | const server = new ExpressServer() 3 | server.start() -------------------------------------------------------------------------------- /images/bas_clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/bas_clone.png -------------------------------------------------------------------------------- /images/cs_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/cs_create.png -------------------------------------------------------------------------------- /images/cs_npm_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/cs_npm_run.png -------------------------------------------------------------------------------- /images/cs_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/cs_ready.png -------------------------------------------------------------------------------- /images/npm_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/npm_start.png -------------------------------------------------------------------------------- /images/base_npm_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/base_npm_run.png -------------------------------------------------------------------------------- /images/node_v_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/node_v_check.png -------------------------------------------------------------------------------- /images/npm_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/npm_install.png -------------------------------------------------------------------------------- /images/npm_run_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/npm_run_dev.png -------------------------------------------------------------------------------- /images/bas_dev_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/bas_dev_space.png -------------------------------------------------------------------------------- /images/bas_npm_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/bas_npm_install.png -------------------------------------------------------------------------------- /images/bas_npm_run_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/bas_npm_run_dev.png -------------------------------------------------------------------------------- /images/cs_npm_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/cs_npm_install.png -------------------------------------------------------------------------------- /images/cs_npm_run_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/sap-community-code-challenge/HEAD/images/cs_npm_run_dev.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "appconfig", 4 | "healthcheck", 5 | "SAPUI" 6 | ], 7 | "SAP HANA Database Explorer.displaySapWebAnalyticsStartupNotification": false 8 | } -------------------------------------------------------------------------------- /app/profilePic/profilePic/view/BusyDialog.fragment.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.0.0] - 2022-02-01 10 | 11 | ### Added 12 | 13 | - Initial release 14 | -------------------------------------------------------------------------------- /app/profilePic/profilePic/model/models.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /*eslint-env es6 */ 3 | sap.ui.define([ 4 | "sap/ui/model/json/JSONModel", 5 | "sap/ui/Device" 6 | ], (JSONModel, Device) => { 7 | "use strict" 8 | 9 | return { 10 | 11 | createDeviceModel: () => { 12 | var oModel = new JSONModel(Device) 13 | oModel.setDefaultBindingMode("OneWay") 14 | return oModel 15 | } 16 | 17 | } 18 | }) -------------------------------------------------------------------------------- /_i18n/messages.properties: -------------------------------------------------------------------------------- 1 | errPort=is not a valid HTTP port value 2 | lagError=Event Loop Lag Exceeded: {0} milliseconds 3 | errFileType=Invalid file type. Only jpg, png and gif image files are allowed. 4 | errFileTooLarge=Uploaded file is too large. Please choose a file less than 20MB in size 5 | appTitle=SAP Community Profile Picture Editor 6 | appDescription=SAP Community Profile Picture Editor / Enhancer 7 | Toolbar1=Choose Selfie Template 8 | Toolbar2=Choose Your Picture and Upload 9 | Toolbar3=Edit and Download Final Image 10 | Upload=Enhance Your Picture 11 | placeholder=Choose File for Upload... 12 | gui.loading=Processing Picture 13 | gui.loadingLong=Please wait ...Processing Picture -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Change this to match your project 3 | "compilerOptions": { 4 | // Tells TypeScript to read JS files, as 5 | // normally they are ignored as source files 6 | "allowJs": true, 7 | // Generate d.ts files 8 | "declaration": false, 9 | // Types should go into this directory. 10 | // Removing this would place the .d.ts files 11 | // next to the .js files 12 | "outDir": "@types", 13 | "rootDir": ".", 14 | "skipLibCheck": true, 15 | "lib": ["ES2020"], 16 | "target": "ES2020", 17 | "moduleResolution": "Node", 18 | "module": "es2020", 19 | "allowSyntheticDefaultImports": true 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /server/express.js: -------------------------------------------------------------------------------- 1 | import logging from '@sap/logging' 2 | import * as loader from '../utils/loader.js' 3 | 4 | /** 5 | * Configure the Express server and load basic functionality such as health checks and security configuration 6 | * @param {Object} app - Express application instance 7 | */ 8 | export default async function (app) { 9 | let appContext = logging.createAppContext({}) 10 | app.logger = appContext.createLogContext().getLogger('/Application') 11 | 12 | app.set('etag', false) 13 | 14 | loader.importFile('server/healthCheck.js', app) 15 | loader.importFile('server/overloadProtection.js', app) 16 | loader.importFile('server/expressSecurity.js', app) 17 | 18 | app.use(logging.middleware({ appContext: appContext, logNetwork: true })) 19 | } -------------------------------------------------------------------------------- /server/expressSecurity.js: -------------------------------------------------------------------------------- 1 | /* import * as passport from 'passport' 2 | import * as xssec from '@sap/xssec' 3 | import * as xsenv from '@sap/xsenv' */ 4 | 5 | /** 6 | * Configure the security aspects of the Express server 7 | * @param {Object} app - Express application instance 8 | */ 9 | export default function (app) { 10 | 11 | //Only needed if service requires authentication and this one won't 12 | 13 | /* 14 | passport.use("JWT", new xssec.JWTStrategy(xsenv.getServices({ 15 | uaa: { 16 | tag: "xsuaa" 17 | } 18 | }).uaa)) 19 | app.use(passport.initialize()) 20 | app.use( 21 | passport.authenticate("JWT", { 22 | session: false 23 | }) 24 | ) */ 25 | } -------------------------------------------------------------------------------- /app/profilePic/profilePic/Component.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /*eslint-env es6 */ 3 | sap.ui.define([ 4 | "sap/ui/core/UIComponent", 5 | "sap/profilePic/model/models" 6 | ], function (UIComponent, models) { 7 | "use strict" 8 | 9 | return UIComponent.extend("profilePic.Component", { 10 | 11 | metadata: { 12 | manifest: "json" 13 | }, 14 | 15 | init: function () { 16 | // call the base component's init function 17 | UIComponent.prototype.init.apply(this, arguments) 18 | 19 | // enable routing 20 | this.getRouter().initialize() 21 | 22 | // set the device model 23 | this.setModel(models.createDeviceModel(), "device") 24 | } 25 | 26 | 27 | }) 28 | }) -------------------------------------------------------------------------------- /routes/frontend.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import fs from 'fs' 3 | 4 | /** 5 | * Route for serving the Frontend UI components 6 | * @param {Object} app - Express application instance 7 | */ 8 | export default function (app) { 9 | 10 | 11 | app.get('/appconfig/fioriSandboxConfig.json', async (req, res) => { 12 | try { 13 | let appConfig = JSON.parse(fs.readFileSync(path.join(app.baseDir, './app/appconfig/fioriSandboxConfig.json'))) 14 | appConfig.applications['profilepic-ui'].title = app.bundle.getText("appTitle") 15 | appConfig.applications["profilepic-ui"].description = app.bundle.getText("appDescription") 16 | res.type("application/json").status(200).send(appConfig) 17 | } catch (error) { 18 | app.logger.error(error) 19 | res.status(500).send(error.toString()) 20 | } 21 | }) 22 | app.use('/profilePic', app.express.static(path.join(app.baseDir, './app/profilePic'))) 23 | } -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { promises as fs } from 'fs' 3 | import showdown from 'showdown' 4 | const {Converter} = showdown 5 | 6 | /** 7 | * Entry Point Route 8 | * @param {Object} app - Express application instance 9 | */ 10 | export default function (app) { 11 | app.use('/images', app.express.static(path.join(app.baseDir, './images'))) 12 | app.use('/i18n', app.express.static(path.join(app.baseDir, './_i18n'))) 13 | 14 | //Load project README.md as the root page of the service 15 | app.get('/', async (req, res) => { 16 | try { 17 | const mdReadMe = await fs.readFile(path.resolve(app.baseDir, "./README.md"), "utf-8") 18 | const converter = new Converter 19 | const html = converter.makeHtml(mdReadMe) 20 | res.type("text/html").status(200).send(html) 21 | } catch (error) { 22 | app.logger.error(error) 23 | res.status(500).send(error.toString()) 24 | } 25 | }) 26 | } -------------------------------------------------------------------------------- /server/healthCheck.js: -------------------------------------------------------------------------------- 1 | import health from '@cloudnative/health-connect' 2 | import Lag from 'event-loop-lag' 3 | 4 | /** 5 | * Configure the Express server and load basic functionality such as health checks and security configuration 6 | * @param {Object} app - Express application instance 7 | */ 8 | export default function (app) { 9 | let healthCheck = new health.HealthChecker() 10 | const lagHealth = () => new Promise((resolve, _reject) => { 11 | let lag = new Lag(1_000) 12 | if (lag() > 40) { 13 | _reject(app.bundle.getText("lagError",[lag()])) 14 | } 15 | resolve() 16 | }) 17 | let lagCheck = new health.LivenessCheck("Event Loop Lag Check", lagHealth) 18 | healthCheck.registerLivenessCheck(lagCheck) 19 | 20 | app.use('/live', health.LivenessEndpoint(healthCheck)) 21 | app.use('/ready', health.ReadinessEndpoint(healthCheck)) 22 | app.use('/health', health.HealthEndpoint(healthCheck)) 23 | app.use('/healthcheck', health.HealthEndpoint(healthCheck)) 24 | } -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/v0.216.0/containers/javascript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version: 16, 14, 12 4 | ARG VARIANT="16-buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} 6 | 7 | # Prepare for apt-based install of Cloud Foundry CLI by adding Cloud Foundry Foundation public key & package repository 8 | # (see https://docs.cloudfoundry.org/cf-cli/install-go-cli.html#pkg-linux). 9 | RUN wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - ; \ 10 | echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list 11 | 12 | # Install extra tools for BTP development & deployment. 13 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 14 | && apt-get -y install --no-install-recommends cf-cli 15 | 16 | # Install global node modules for SAP BTP development. 17 | RUN su node -c "npm install -g yo mbt typescript" -------------------------------------------------------------------------------- /app/appconfig/fioriSandboxConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultRenderer": "fiori2", 3 | "bootstrapPlugins": { 4 | "RuntimeAuthoringPlugin": { 5 | "component": "sap.ushell.plugins.rta" 6 | }, 7 | "PersonalizePlugin": { 8 | "component": "sap.ushell.plugins.rta-personalize" 9 | } 10 | }, 11 | "services": { 12 | "NavTargetResolution": { 13 | "config": { 14 | "allowTestUrlComponentConfig": true, 15 | "enableClientSideTargetResolution": true 16 | } 17 | }, 18 | "EndUserFeedback": { 19 | "adapter": { 20 | "config": { 21 | "enabled": true 22 | } 23 | } 24 | } 25 | }, 26 | "applications": { 27 | "profilepic-ui": { 28 | "title": "", 29 | "description": "", 30 | "additionalInformation": "SAPUI5.Component=sap.profilePic", 31 | "applicationType": "URL", 32 | "url": "./profilePic", 33 | "navigationMode": "embedded" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /utils/texts.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | const require = createRequire(import.meta.url) 3 | const TextBundle = require('@sap/textbundle').TextBundle 4 | import langParser from 'accept-language-parser' 5 | import path from 'path' 6 | 7 | /** 8 | * Get Locale from HTTP Request Header 9 | * @param {*} req - HTTP Request object from Express 10 | * @returns {string} 11 | */ 12 | export function getLocale(req) { 13 | if (req) { 14 | let lang = req.headers["accept-language"] 15 | if (!lang) { 16 | return 17 | } 18 | let arr = langParser.parse(lang) 19 | if (!arr || arr.length < 1) { 20 | return 21 | } 22 | let locale = arr[0].code 23 | if (arr[0].region) { 24 | locale += "-" + arr[0].region 25 | } 26 | return locale 27 | }else{ 28 | return 29 | } 30 | } 31 | 32 | /** 33 | * Get Text Bundle from sap/textbundle 34 | * @param {*} req - HTTP Request object from Express 35 | * @returns @typeof TextBundle - instance of sap/textbundle 36 | */ 37 | export function getBundle(req) { 38 | return new TextBundle(path.resolve(process.cwd(), "./_i18n/messages"), getLocale(req)) 39 | } 40 | -------------------------------------------------------------------------------- /server/overloadProtection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add overload protection to the Express instance 3 | * @param {Object} app - Express application instance 4 | */ 5 | export default async function (app) { 6 | const protectCfg = { 7 | production: process.env.NODE_ENV === 'production', // if production is false, detailed error messages are exposed to the client 8 | clientRetrySecs: 1, // Client-Retry header, in seconds (0 to disable) [default 1] 9 | sampleInterval: 5, // sample rate, milliseconds [default 5] 10 | maxEventLoopDelay: 100, // maximum detected delay between event loop ticks [default 42] 11 | maxHeapUsedBytes: 0, // maximum heap used threshold (0 to disable) [default 0] 12 | maxRssBytes: 0, // maximum rss size threshold (0 to disable) [default 0] 13 | errorPropagationMode: false, // dictate behavior: take over the response 14 | logging: (message) => { 15 | app.logger.error(message) 16 | } 17 | // or propagate an error to the framework [default false] 18 | } 19 | const { default: OverloadProtection } = await import('overload-protection') 20 | const protect = OverloadProtection('express', protectCfg) 21 | app.use(protect) 22 | } -------------------------------------------------------------------------------- /utils/loader.js: -------------------------------------------------------------------------------- 1 | import { existsSync as fileExists } from 'fs' 2 | import * as path from 'path' 3 | import g from "glob" 4 | import { promisify } from 'util' 5 | const glob = promisify(g) 6 | 7 | /** 8 | * Dynamic Import on single project file (with file exists check) 9 | * @param {String} file - JavaScript file to load and import 10 | * @param {Object} app - Express Application Instance 11 | */ 12 | export async function importFile(file, app) { 13 | let targetFile = path.join(app.baseDir, file) 14 | if (fileExists(targetFile)) { 15 | const { default: importFunction } = await import(`file://${targetFile}`) 16 | importFunction(app) 17 | } 18 | } 19 | 20 | /** 21 | * Dynamic Import on folder full of project files 22 | * @param {String} folder - Project folder to load and import all js files within 23 | * @param {Object} app - Express Application Instance 24 | */ 25 | export async function importFolder(folder, app) { 26 | 27 | let routesDir = path.join(app.baseDir, folder) 28 | let files = await glob(routesDir) 29 | // this.routerFiles = files 30 | if (files.length !== 0) { 31 | for (let file of files) { 32 | const { default: Route } = await import(`file://${file}`) 33 | Route(app) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/profilePic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 25 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/profilePic/profilePic/view/App.view.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |