├── .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 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.devcontainer/.devcontainer:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.216.0/containers/javascript-node
3 | {
4 | "name": "SAP Community Activity Badges Contributor Devcontainer",
5 | "build": {
6 | "dockerfile": "Dockerfile",
7 | // Update 'VARIANT' to pick a Node version: 12, 14, 16
8 | "args": { "VARIANT": "16" }
9 | },
10 |
11 | // Set *default* container specific settings.json values on container create.
12 | "settings": {},
13 |
14 | // Add the IDs of extensions you want installed when the container is created.
15 | "extensions": [
16 | "dbaeumer.vscode-eslint",
17 | "hookyqr.beautify",
18 | "coenraads.bracket-pair-colorizer-2",
19 | "eamodio.gitlens",
20 | "yzhang.markdown-all-in-one",
21 | "fivepointseven.node-version",
22 | "bengreenier.vscode-node-readme",
23 | "christian-kohler.path-intellisense",
24 | "humao.rest-client",
25 | "pflannery.vscode-versionlens",
26 | "visualstudioexptteam.vscodeintellicode",
27 | "vscode-icons-team.vscode-icons",
28 | "dotjoshjohnson.xml"
29 | ],
30 |
31 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
32 | "forwardPorts": [ 4000 ],
33 |
34 | // Use 'postCreateCommand' to run commands after the container is created.
35 | // "postCreateCommand": "yarn install",
36 |
37 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
38 | "remoteUser": "node"
39 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sap-community-code-challenge",
3 | "version": "1.0.0",
4 | "description": "SAP Community Code Challenge: This repository is a template project/solution showing how to enhance your SAP Community profile picture with a new border.",
5 | "main": "index.js",
6 | "engines": {
7 | "node": "^12.0.0 || ^14.0.0 || ^16.0.0"
8 | },
9 | "type": "module",
10 | "scripts": {
11 | "start": "node index",
12 | "dev": "nodemon index",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/SAP-samples/sap-community-code-challenge.git"
18 | },
19 | "keywords": [
20 | "sap-community",
21 | "sample",
22 | "code-challenge",
23 | "nodejs",
24 | "sap-btp"
25 | ],
26 | "author": "SAP",
27 | "license": "SEE LICENSE IN LICENSES",
28 | "bugs": {
29 | "url": "https://github.com/SAP-samples/sap-community-code-challenge/issues"
30 | },
31 | "homepage": "https://github.com/SAP-samples/sap-community-code-challenge#readme",
32 | "dependencies": {
33 | "@cloudnative/health-connect": "^2.1.0",
34 | "@sap/logging": "^6.1.0",
35 | "@sap/textbundle": "^4.1.0",
36 | "accept-language-parser": "^1.5.0",
37 | "event-loop-lag": "^1.4.0",
38 | "express": "^4.17.2",
39 | "glob": "^7.2.0",
40 | "lodash": "^4.17.21",
41 | "multer": "^1.4.4",
42 | "overload-protection": "^1.2.3",
43 | "serve-favicon": "^2.5.0",
44 | "sharp": "^0.30.1",
45 | "showdown": "^1.9.1"
46 | },
47 | "devDependencies": {
48 | "@types/glob": "^7.2.0",
49 | "@types/node": "^17.0.17",
50 | "nodemon": "^2.0.15"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: [sap-community-code-challenge]
3 | Upstream-Contact: Thomas Jung
4 | Source: https://github.com/sap-samples/sap-community-code-challenge
5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of
6 | SAP or third-party products or services developed outside of this project
7 | (“External Products”).
8 | “APIs” means application programming interfaces, as well as their respective
9 | specifications and implementing code that allows software to communicate with
10 | other software.
11 | API Calls to External Products are not licensed under the open source license
12 | that governs this project. The use of such API Calls and related External
13 | Products are subject to applicable additional agreements with the relevant
14 | provider of the External Products. In no event shall the open source license
15 | that governs this project grant any rights in or to any External Products,or
16 | alter, expand or supersede any terms of the applicable additional agreements.
17 | If you have a valid license agreement with SAP for the use of a particular SAP
18 | External Product, then you may make use of any API Calls included in this
19 | project’s code for that SAP External Product, subject to the terms of such
20 | license agreement. If you do not have a valid license agreement for the use of
21 | a particular SAP External Product, then you may only make use of any API Calls
22 | in this project for that SAP External Product for your internal, non-productive
23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants
24 | you any rights to use or access any SAP External Product, or provide any third
25 | parties the right to use of access any SAP External Product, through API Calls.
26 |
27 | Files: *
28 | Copyright: 2022 SAP SE or an SAP affiliate company and sap-community-awareness-code-challenge contributors
29 | License: Apache-2.0
30 |
--------------------------------------------------------------------------------
/express-server.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import express from 'express'
3 | import * as path from 'path'
4 | import favicon from 'serve-favicon'
5 | import { existsSync as fileExists } from 'fs'
6 |
7 | import * as texts from './utils/texts.js'
8 | import * as loader from './utils/loader.js'
9 |
10 | /**
11 | * Build the Express Server and Setup Route Loading dynamically from a /routes folder
12 | */
13 | export default class ExpressServer {
14 |
15 | /**
16 | * @constructor
17 | * Express Server Constructor
18 | */
19 | constructor() {
20 | /** @typeof TextBundle - instance of sap/textbundle */
21 | this.bundle = texts.getBundle()
22 | /** @type {Number} - Default Port*/
23 | this.port = parseInt(process.env.PORT) || 4_000
24 | if (!(/^[1-9]\d*$/.test(this.port.toString()) && 1 <= 1 * this.port && 1 * this.port <= 65_535)) {
25 | throw new Error(`${this.port} ${this.bundle.getText("errPort")}`)
26 | }
27 |
28 | /** @type {String} - Directory relative to root of the project*/
29 | this.baseDir = process.cwd()
30 | this.routes = []
31 | this.app = express()
32 | this.app.bundle = this.bundle
33 | this.app.express = express
34 |
35 | //Load Service/Site Favicon
36 | let faviconFile = path.join(this.baseDir, 'images', 'favicon.ico')
37 | if (fileExists(faviconFile)) {
38 | this.app.use(favicon(faviconFile))
39 | }
40 | }
41 |
42 |
43 | /**
44 | * Start Express Server
45 | */
46 | async start() {
47 | let app = this.app
48 | app.baseDir = this.baseDir
49 |
50 | //Load express.js
51 | loader.importFile('server/express.js', app)
52 |
53 | //Load routes
54 | loader.importFolder('routes/**/*.js', app)
55 |
56 | this.httpServer = app.listen(this.port)
57 | console.log(`Express Server Now Running On http://localhost:${this.port}/`)
58 | }
59 |
60 | /**
61 | * Stop Express Server
62 | */
63 | stop() {
64 | this.httpServer.close()
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | /mta_archives
107 |
108 | /app/*.zip
--------------------------------------------------------------------------------
/app/profilePic/profilePic/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.33.0",
3 | "sap.app": {
4 | "id": "profilePic",
5 | "type": "application",
6 | "i18n": "/i18n/messages.properties",
7 | "applicationVersion": {
8 | "version": "1.0.0"
9 | },
10 | "title": "{{appTitle}}",
11 | "description": "{{appDescription}}",
12 | "sourceTemplate": {
13 | "id": "html5moduletemplates.basicSAPUI5ApplicationProjectModule",
14 | "version": "1.40.12"
15 | }
16 | },
17 |
18 | "sap.ui": {
19 | "technology": "UI5",
20 | "icons": {
21 | "icon": "/favicon.ico",
22 | "favIcon": "/favicon.ico",
23 | "phone": "",
24 | "phone@2": "",
25 | "tablet": "",
26 | "tablet@2": ""
27 | },
28 | "deviceTypes": {
29 | "desktop": true,
30 | "tablet": true,
31 | "phone": true
32 | }
33 | },
34 |
35 | "sap.ui5": {
36 | "flexEnabled": false,
37 | "rootView": {
38 | "viewName": "profilePic.view.App",
39 | "type": "XML",
40 | "async": true,
41 | "id": "App"
42 | },
43 | "dependencies": {
44 | "minUI5Version": "1.98.0",
45 | "libs": {
46 | "sap.ui.core": {},
47 | "sap.m": {},
48 | "sap.ui.layout": {}
49 | }
50 | },
51 | "contentDensities": {
52 | "compact": true,
53 | "cozy": true
54 | },
55 | "models": {
56 | "config": {
57 | "type": "sap.ui.model.json.JSONModel"
58 | },
59 | "i18n": {
60 | "type": "sap.ui.model.resource.ResourceModel",
61 | "settings": {
62 | "bundleUrl": "/i18n/messages.properties"
63 | }
64 | }
65 | },
66 | "resources": {
67 | "css": [{
68 | "uri": "../css/style.css"
69 | }]
70 | },
71 | "routing": {
72 | "config": {
73 | "routerClass": "sap.m.routing.Router",
74 | "viewType": "XML",
75 | "async": true,
76 | "viewPath": "profilePic.view",
77 | "controlAggregation": "pages",
78 | "controlId": "app",
79 | "clearControlAggregation": false
80 | },
81 | "routes": [{
82 | "name": "RouteApp",
83 | "pattern": "RouteApp",
84 | "target": ["TargetApp"]
85 | }],
86 | "targets": {
87 | "TargetApp": {
88 | "viewType": "XML",
89 | "transition": "slide",
90 | "clearControlAggregation": false,
91 | "viewId": "App",
92 | "viewName": "App"
93 | }
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/routes/profilePic.js:
--------------------------------------------------------------------------------
1 | import sharp from 'sharp'
2 | import multer from 'multer'
3 | import lodash from 'lodash'
4 | import * as svg from '../utils/svgRender.js'
5 | import { promises as fs } from 'fs'
6 | import * as path from 'path'
7 |
8 | /**
9 | * Route for handing upload, manipulation, and download of the profile picture
10 | * @param {Object} app - Express application instance
11 | */
12 | export default function (app) {
13 |
14 | /**
15 | * Load Local Image as Base64
16 | * @param {string} image
17 | * @returns {Promise}
18 | */
19 | async function loadImageB64(image) {
20 |
21 | return await fs.readFile(path.resolve(app.baseDir, image), { encoding: 'base64' })
22 | }
23 |
24 | const limits = {
25 | files: 1, // allow only 1 file per request
26 | fileSize: 1024 * 1024 * 20, // 20 MB (max file size)
27 | }
28 |
29 | let fileFilter = async (req, file, cb) => {
30 | // supported image file mime types
31 | let allowedMimes = ['image/jpeg', 'image/png', 'image/gif']
32 |
33 | if (lodash.includes(allowedMimes, file.mimetype)) {
34 | // allow supported image files
35 | cb(null, true)
36 | } else {
37 | // throw error for invalid files
38 | cb(new Error(app.bundle.getText("errFileType")))
39 | }
40 | }
41 |
42 | // setup multer
43 | const storage = multer.memoryStorage()
44 | let upload = multer({
45 | storage: storage,
46 | limits: limits,
47 | fileFilter: fileFilter
48 | })
49 | const uploadHandler = upload.any()
50 | app.post('/upload_profile_pic', async (req, res, next) => {
51 | await uploadHandler(req, res, async (err) => {
52 | console.log(`in upload handler`)
53 | if (err instanceof multer.MulterError) {
54 | if (err.toString() == 'MulterError: File too large') {
55 | return res.send(app.bundle.getText("errFileTooLarge")).status(500)
56 | } else {
57 | return res.send(err.toString()).status(500)
58 | }
59 | } else if (err) {
60 | return res.send(err.toString()).status(500)
61 | }
62 |
63 | //We only allow one file but still pull it out of the files array
64 | let file
65 | req.files.forEach((f) => {
66 | file = f
67 | })
68 |
69 | //Convert the uploaded content to PNG, rotate based upon metadata and transfer it to a buffer
70 | const uploadContent = await sharp(file.buffer).rotate().png().toBuffer()
71 | const uploadContentMeta = await sharp(uploadContent).metadata()
72 | let body =
73 | svg.svgHeader(uploadContentMeta.width, uploadContentMeta.height) +
74 | svg.svgItem(0, 0, 0, uploadContent.toString('base64'), uploadContentMeta.height, uploadContentMeta.width, true) +
75 | svg.svgItem(0, 0, 0, await loadImageB64('./images/boarder.png'), uploadContentMeta.height, uploadContentMeta.width, true) +
76 | svg.svgEnd()
77 |
78 | const png = await sharp(Buffer.from(body)).png().toBuffer()
79 | const pngOut = await png.toString('base64')
80 | res.type("image/png").status(200).send(pngOut)
81 |
82 | })
83 | })
84 | }
--------------------------------------------------------------------------------
/utils/svgRender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module svgRender - Utilities to help with the SVG Rendering
3 | */
4 |
5 | /**
6 | * svg Header
7 | * @param {number} width
8 | * @param {number} height
9 | * @returns {string}
10 | */
11 | export function svgHeader(width, height) {
12 | let content =
13 | `
136 | `
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/app/profilePic/profilePic/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | "use strict";
4 | sap.ui.define([
5 | "profilePic/controller/BaseController",
6 | "sap/m/MessageToast",
7 | "sap/ui/core/Core",
8 | "sap/ui/model/json/JSONModel",
9 | "sap/ui/Device",
10 | "sap/suite/ui/commons/library"
11 | ],
12 | function (BaseController, MessageToast, oCore, JSONModel, Device, SuiteLibrary) {
13 |
14 | return BaseController.extend("profilePic.controller.App", {
15 | onInit: function () {
16 | var oImageEditor = this.getView().byId("image"),
17 | oModel = new JSONModel({
18 | blocked: true
19 | })
20 |
21 | this.getView().setModel(oModel)
22 | if (!Device.browser.msie) {
23 | // svg files are not supported in Internet Explorer
24 | oImageEditor.setCustomShapeSrc(sap.ui.require.toUrl("sap/suite/ui/commons/statusindicator") + "/shapes/bulb.svg")
25 | }
26 | },
27 |
28 | uploadPressed: async function (oEvent) {
29 | let view = this.getView()
30 | let controller = view.getController()
31 | let oFileUploader = view.byId("fileToUpload")
32 | if (!oFileUploader.getValue()) {
33 | MessageToast.show("Choose a file first")
34 | return
35 | }
36 | let param = view.byId("uploadParam")
37 | //param.setValue(oInput.getActivePage())
38 | oFileUploader.getParameters()
39 | var oImageEditor = this.getView().byId("image")
40 | oImageEditor.applyVisibleCrop()
41 | console.log(oImageEditor.getMode())
42 | oFileUploader.getProcessedBlobsFromArray = async function (oBlobs) {
43 | return new Promise(async (resolve, reject) => {
44 | let newBlob = await oImageEditor.getImageAsBlob()
45 | resolve([newBlob])
46 | })
47 | }
48 | controller.startBusy()
49 | oFileUploader.upload(true)
50 | },
51 |
52 | uploadStart: async function (oEvent) {
53 | let view = this.getView()
54 | let controller = view.getController()
55 | controller.startBusy()
56 | },
57 |
58 | uploadComplete: async function (oEvent) {
59 | let view = this.getView()
60 | let controller = view.getController()
61 | let dataURL = "data:image/png;base64," + oEvent.getParameters().responseRaw
62 | let oImageEditor = view.byId("image")
63 | await oImageEditor.setSrc(dataURL)
64 | controller.endBusy(controller)
65 | },
66 |
67 | onSaveAsPress: async function () {
68 | let view = this.getView()
69 | let controller = view.getController()
70 | let oImageEditor = view.byId("image")
71 | oImageEditor.openSaveDialog()
72 | controller.openUrl('https://people.sap.com/', true)
73 |
74 | },
75 | onImageLoaded: async function (oEvent) {
76 | let view = this.getView()
77 | let oImageEditor = view.byId("image")
78 | oImageEditor.zoomToFit()
79 | oImageEditor.setCropAreaByRatio(1, 1)
80 | oImageEditor.setMode(SuiteLibrary.ImageEditorMode.CropEllipse)
81 | console.log(oImageEditor.getMode())
82 |
83 |
84 |
85 | },
86 | onFileChange: async function (oEvent) {
87 | var oFile = oEvent.getParameter("files")[0],
88 | oImageEditor = this.getView().byId("image")
89 | if (!oFile) {
90 | return
91 | }
92 | this.getView().getModel().setProperty("/blocked", true)
93 | await oImageEditor.setSrc(oFile)
94 |
95 | }
96 | })
97 | }
98 | )
--------------------------------------------------------------------------------
/app/profilePic/profilePic/controller/BaseController.js:
--------------------------------------------------------------------------------
1 | /*global history */
2 | /* eslint-disable no-undef */
3 | sap.ui.define([
4 | "sap/ui/core/mvc/Controller",
5 | "sap/ui/core/routing/History",
6 | "sap/ui/core/Fragment",
7 | "sap/ui/core/syncStyleClass",
8 | ], function (Controller, History, Fragment, syncStyleClass) {
9 | "use strict";
10 |
11 | return Controller.extend("profilePic.controller.BaseController", {
12 | /**
13 | * Convenience method for accessing the router in every controller of the application.
14 | * @public
15 | * @returns {sap.ui.core.routing.Router} the router for this component
16 | */
17 | getRouter: function () {
18 | return this.getOwnerComponent().getRouter();
19 | },
20 |
21 | /**
22 | * Convenience method for getting the view model by name in every controller of the application.
23 | * @public
24 | * @param {string} sName the model name
25 | * @returns {sap.ui.model.Model} the model instance
26 | */
27 | getModel: function (sName) {
28 | return this.getView().getModel(sName);
29 | },
30 |
31 | /**
32 | * Convenience method for setting the view model in every controller of the application.
33 | * @public
34 | * @param {sap.ui.model.Model} oModel the model instance
35 | * @param {string} sName the model name
36 | * @returns {sap.ui.mvc.View} the view instance
37 | */
38 | setModel: function (oModel, sName) {
39 | return this.getView().setModel(oModel, sName);
40 | },
41 |
42 | /**
43 | * Convenience method for getting the resource bundle.
44 | * @public
45 | * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
46 | */
47 | getResourceBundle: function () {
48 | return this.getOwnerComponent().getModel("i18n").getResourceBundle();
49 | },
50 |
51 | /**
52 | * Event handler for navigating back.
53 | * It there is a history entry we go one step back in the browser history
54 | * If not, it will replace the current entry of the browser history with the master route.
55 | * @public
56 | */
57 | onNavBack: function () {
58 | var sPreviousHash = History.getInstance().getPreviousHash();
59 |
60 | if (sPreviousHash !== undefined) {
61 | history.go(-1);
62 | } else {
63 | this.getRouter().navTo("master", {}, true)
64 | }
65 | },
66 |
67 |
68 | openUrl: function (url, newTab) {
69 | // Require the URLHelper and open the URL in a new window or tab (same as _blank):
70 | sap.ui.require(["sap/m/library"], ({ URLHelper }) => URLHelper.redirect(url, newTab));
71 | },
72 |
73 |
74 | startBusy: function () {
75 | if (!this._pBusyDialog) {
76 | this._pBusyDialog = Fragment.load({
77 | name: "profilePic.view.BusyDialog",
78 | controller: this
79 | }).then(function (oBusyDialog) {
80 | this.getView().addDependent(oBusyDialog)
81 | syncStyleClass("sapUiSizeCompact", this.getView(), oBusyDialog)
82 | return oBusyDialog
83 | }.bind(this))
84 | }
85 |
86 | this._pBusyDialog.then(function (oBusyDialog) {
87 | oBusyDialog.open()
88 | }.bind(this))
89 | },
90 | endBusy: function (oController) {
91 | if (oController._pBusyDialog) {
92 | oController._pBusyDialog.then(function (oBusyDialog) {
93 | oBusyDialog.close()
94 | })
95 | }
96 | },
97 |
98 | onErrorCall: function (oError, oController) {
99 | if (oController) {
100 | oController.endBusy(oController)
101 | }
102 | sap.ui.require(["sap/m/MessageBox"], (MessageBox) => {
103 | console.log(oError)
104 | if (oError.statusCode === 500 || oError.statusCode === 400 || oError.statusCode === "500" || oError.statusCode === "400" || oError.status === 500) {
105 | var errorRes = oError.responseText
106 | MessageBox.alert(errorRes)
107 | return
108 | } else {
109 | MessageBox.alert(oError.statusText)
110 | return
111 | }
112 | })
113 | }
114 |
115 | })
116 |
117 | }
118 | )
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d)
2 |
3 | # SAP Community Code Challenge
4 |
5 | [](https://api.reuse.software/info/github.com/SAP-samples/sap-community-code-challenge)
6 |
7 |
8 | [](https://badges.pufler.dev)
9 | [](https://badges.pufler.dev)
10 | [](https://open.vscode.dev/SAP-samples/sap-community-code-challenge)
11 |
12 | [](https://badges.pufler.dev)
13 |
14 | The [change log](https://github.com/SAP-samples/sap-community-code-challenge/blob/main/CHANGELOG.md) describes notable changes in this package.
15 |
16 | ## Description
17 |
18 | SAP Community Code Challenge: This repository is a template project/solution showing how to enhance your SAP Community profile picture with a new border.
19 |
20 | Some of the technology contained within this project:
21 |
22 | * [Node.js](https://nodejs.org/en/about/)
23 | * [SAPUI5 Image Editor Control](https://sapui5.hana.ondemand.com/#/entity/sap.suite.ui.commons.imageeditor)
24 | * [Sharp - High performance Node.js image processing](https://github.com/lovell/sharp)
25 | * [SVG Scalable Vector Graphics](https://developer.mozilla.org/en-US/docs/Web/SVG)
26 |
27 | ## Challenges
28 |
29 | Although this project is a complete code sample, it's really only intended to be the starting point. It's part of a code challenge; which means have some fun and learn at the same time. For the full details on the Code Challenge please see this [SAP Community Discussion Thread](https://groups.community.sap.com/t5/coffee-corner/gh-p/Coffee-Corner). But let's detail right here some of the possible challenges you might undertake using this starting point.
30 |
31 | The whole project can run locally. It's a [SAPUI5 app](/profilePic/#profilepic-ui) where you start by uploading the base picture you want. You can do some editing on it. Then you set it to a circular crop (since that's is what the SAP Community profile pic requires). When you press enhance picture it's uploaded to the "server" there we apply the border. Then you can save the final file (or edit if further if you want). Upon save it automatically opens your SAP Community Profile in another browser tab. There you can upload your newly enhanced profile picture. Now that you understand what the code sample does; here are the challenges.
32 |
33 | ### Run the Project Locally
34 |
35 | 1. Ensure that you have [Node.js](https://nodejs.org/en/about/) ver 12, 14 or 16 installed locally.
36 | 
37 |
38 | 2. [Clone this repository](https://github.com/SAP-samples/sap-community-code-challenge.git)
39 | 
40 |
41 | 3. From the root of the project in a terminal issue the command `npm install`. This will install all project dependencies into the /node_modules folder.
42 | 
43 |
44 | 4. Once all dependencies are installed, issue the command `npm start` from the terminal. This will start a local web server. You can test the application locally via the url: [http://localhost:4000/profilepic/](http://localhost:4000/profilepic/)
45 | 
46 |
47 | 5. [Optional]: If you plan to make changes to the project code, there is a command `npm run dev` that you can use for testing. This command will automatically restart the server with each code change you make. This can be quite convenient when making frequent changes and constantly retesting.
48 | 
49 |
50 | ### Run the Project on the SAP Business Application Studio
51 |
52 | 1. Create or reuse an SAP Business Application Studio Dev Space of type "Full Stack Cloud Development". This will ensure that you have the correct Node.js runtime already installed into the development environment.
53 | 
54 |
55 | 2. Clone [this repository](https://github.com/SAP-samples/sap-community-code-challenge.git) into the workspace.
56 | 
57 |
58 | 3. From the root of the project in a terminal issue the command `npm install`. This will install all project dependencies into the /node_modules folder.
59 | 
60 |
61 | 4. Once all dependencies are installed, issue the command `npm start` from the terminal. This will start a local web server. You can test the application locally by using the "Expose and Open" button
62 | 
63 |
64 | 5. [Optional]: If you plan to make changes to the project code, there is a command `npm run dev` that you can use for testing. This command will automatically restart the server with each code change you make. This can be quite convenient when making frequent changes and constantly retesting.
65 | 
66 |
67 | ### Run the Project in a Dev Container or Codespaces
68 |
69 | 1. From GitHub choose the option to create a new codespace.
70 | 
71 |
72 | 2. You can then use this codespace from the browser or open it remotely in your locally VSCode installation. The codespace will be pre-configured with the correct Node.js runtime and already has the project cloned into it.
73 | 
74 |
75 | 3. From the root of the project in a terminal issue the command `npm install`. This will install all project dependencies into the /node_modules folder.
76 | 
77 |
78 | 4. Once all dependencies are installed, issue the command `npm start` from the terminal. This will start a local web server. You can test the application locally by using the "Open in Browser" button
79 | 
80 |
81 | 5. [Optional]: If you plan to make changes to the project code, there is a command `npm run dev` that you can use for testing. This command will automatically restart the server with each code change you make. This can be quite convenient when making frequent changes and constantly retesting.
82 | 
83 |
84 | ### Learn about SVG - Change the Enhancement
85 |
86 | Create your own profile enhancement. Maybe instead of a border it adds your profile name, badges, etc.
87 |
88 | ### Port the Whole Project to Another Programming Language
89 |
90 | Try and port the whole project to another programming language. I'm sure we will let lots of people converting it to ABAP.
91 |
92 | ### Multiple Image Enhancement Options in the UI
93 |
94 | Extend the app to offer multiple server side enhancements but then extend the SAPUI5 frontend to allow the end user to choose the enhancement type. The trick there is sending the choice along with the upload (it can be done but its tricky). That challenge would be focused on UI5 skills.
95 |
96 | ## Requirements
97 |
98 | Node.js version 12.x, 14.x, or 16.x [https://nodejs.org/en/download/](https://nodejs.org/en/download/)
99 |
100 | ## Download and Installation
101 |
102 | See [Challenges](#challenges)
103 |
104 | ## Known Issues
105 |
106 | None
107 |
108 | ## How to obtain support
109 |
110 | [Create an issue](https://github.com/SAP-samples/sap-community-awareness-code-challenge/issues) in this repository if you find a bug or have questions about the content.
111 |
112 | For additional support, [ask a question in SAP Community](https://answers.sap.com/questions/ask.html).
113 |
114 | ## Contributing
115 |
116 | If you wish to contribute code, offer fixes or improvements, please send a pull request. Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/).
117 |
118 | ## License
119 |
120 | Copyright (c) 2022 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](https://github.com/SAP-samples/sap-community-code-challenge/blob/main/LICENSES/Apache-2.0.txt) file.
121 |
--------------------------------------------------------------------------------
/LICENSES/Apache-2.0.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2022 SAP SE
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------