├── packages ├── api │ ├── migrations │ │ └── .gitignore │ ├── src │ │ ├── Controller │ │ │ ├── .gitignore │ │ │ ├── HomeController.php │ │ │ └── Api │ │ │ │ ├── UserController.php │ │ │ │ ├── ConfigController.php │ │ │ │ └── ContactController.php │ │ ├── Entity │ │ │ ├── .gitignore │ │ │ ├── Config.php │ │ │ ├── Traits │ │ │ │ └── TimestampTrait.php │ │ │ └── Contact.php │ │ ├── Repository │ │ │ ├── .gitignore │ │ │ ├── ConfigRepository.php │ │ │ ├── ContactRepository.php │ │ │ └── UserRepository.php │ │ ├── DataFixtures │ │ │ ├── AppFixtures.php │ │ │ └── UserFixtures.php │ │ ├── Form │ │ │ ├── ConfigType.php │ │ │ ├── ContactType.php │ │ │ ├── RegisterType.php │ │ │ └── SettingsType.php │ │ ├── Security │ │ │ ├── Voter │ │ │ │ ├── ProgramEditVoter.php │ │ │ │ └── ProgramViewVoter.php │ │ │ └── ApiKeyUserProvider.php │ │ ├── Services │ │ │ ├── ContactService.php │ │ │ ├── ConfigService.php │ │ │ └── UserService.php │ │ ├── Doctrine │ │ │ └── CurrentUserExtension.php │ │ └── Kernel.php │ ├── translations │ │ └── .gitignore │ ├── config │ │ ├── packages │ │ │ ├── test │ │ │ │ ├── twig.yaml │ │ │ │ ├── validator.yaml │ │ │ │ ├── framework.yaml │ │ │ │ ├── web_profiler.yaml │ │ │ │ └── monolog.yaml │ │ │ ├── mailer.yaml │ │ │ ├── twig.yaml │ │ │ ├── prod │ │ │ │ ├── routing.yaml │ │ │ │ ├── deprecations.yaml │ │ │ │ ├── monolog.yaml │ │ │ │ └── doctrine.yaml │ │ │ ├── sensio_framework_extra.yaml │ │ │ ├── dev │ │ │ │ ├── web_profiler.yaml │ │ │ │ ├── debug.yaml │ │ │ │ └── monolog.yaml │ │ │ ├── translation.yaml │ │ │ ├── doctrine_migrations.yaml │ │ │ ├── routing.yaml │ │ │ ├── validator.yaml │ │ │ ├── api_platform.yaml │ │ │ ├── nelmio_cors.yaml │ │ │ ├── notifier.yaml │ │ │ ├── lexik_jwt_authentication.yaml │ │ │ ├── framework.yaml │ │ │ ├── doctrine_extensions.yaml │ │ │ ├── doctrine.yaml │ │ │ ├── cache.yaml │ │ │ ├── nyholm_psr7.yaml │ │ │ └── security.yaml │ │ ├── routes.yaml │ │ ├── routes │ │ │ ├── api_platform.yaml │ │ │ ├── dev │ │ │ │ ├── framework.yaml │ │ │ │ └── web_profiler.yaml │ │ │ └── annotations.yaml │ │ ├── bundles.php │ │ └── services.yaml │ ├── README.md │ ├── docker-compose.override.exemple │ ├── public │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── icon-48x48.png │ │ │ ├── icon-72x72.png │ │ │ ├── icon-96x96.png │ │ │ ├── icon-144x144.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-256x256.png │ │ │ ├── icon-384x384.png │ │ │ └── icon-512x512.png │ │ └── index.php │ ├── .env.test │ ├── docker-compose.yml │ ├── tests │ │ └── bootstrap.php │ ├── templates │ │ ├── mails │ │ │ └── contact.html.twig │ │ └── base.html.twig │ ├── bin │ │ ├── phpunit │ │ └── console │ ├── .gitignore │ ├── package.json │ ├── LICENSE.md │ ├── phpunit.xml.dist │ ├── .env │ ├── Makefile │ └── composer.json ├── front │ ├── src │ │ ├── bridges │ │ │ ├── console.js │ │ │ └── axios.js │ │ ├── models │ │ │ ├── index.js │ │ │ └── log.js │ │ ├── assets │ │ │ ├── styles │ │ │ │ ├── _images.scss │ │ │ │ ├── _section.scss │ │ │ │ ├── _custom-forms.scss │ │ │ │ ├── utilities.scss │ │ │ │ ├── _variables.scss │ │ │ │ ├── badge.scss │ │ │ │ ├── reboot.scss │ │ │ │ ├── _card.scss │ │ │ │ ├── themes │ │ │ │ │ ├── _dark.scss │ │ │ │ │ ├── _light.scss │ │ │ │ │ └── _sepia.scss │ │ │ │ ├── index.scss │ │ │ │ ├── _themes.scss │ │ │ │ ├── _app.scss │ │ │ │ ├── _timeline.scss │ │ │ │ ├── _nav.scss │ │ │ │ ├── _forms.scss │ │ │ │ ├── _navbar.scss │ │ │ │ ├── _fonts.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _user.scss │ │ │ │ └── _sidebar.scss │ │ │ ├── images │ │ │ │ ├── logo.png │ │ │ │ └── logo.svg │ │ │ └── fonts │ │ │ │ ├── Jost-400-Book.otf │ │ │ │ ├── Jost-600-Semi.otf │ │ │ │ ├── Jost-700-Bold.otf │ │ │ │ ├── Jost-500-Medium.otf │ │ │ │ ├── Jost-400-BookItalic.otf │ │ │ │ ├── Jost-600-SemiItalic.otf │ │ │ │ ├── Jost-700-BoldItalic.otf │ │ │ │ └── Jost-500-MediumItalic.otf │ │ ├── reducers │ │ │ ├── env │ │ │ │ ├── actions-types.js │ │ │ │ ├── actions.js │ │ │ │ └── index.js │ │ │ ├── user │ │ │ │ ├── actions-types.js │ │ │ │ ├── index.js │ │ │ │ └── actions.js │ │ │ ├── logs │ │ │ │ ├── actions-types.js │ │ │ │ ├── actions.js │ │ │ │ └── index.js │ │ │ ├── app │ │ │ │ ├── actions-types.js │ │ │ │ ├── index.js │ │ │ │ └── actions.js │ │ │ ├── index.js │ │ │ ├── auth │ │ │ │ ├── actions-types.js │ │ │ │ └── index.js │ │ │ ├── contact │ │ │ │ └── actions.js │ │ │ └── config │ │ │ │ └── actions.js │ │ ├── utils │ │ │ ├── server.js │ │ │ ├── create-store.js │ │ │ ├── index.js │ │ │ ├── copy-text-to-clipboard.js │ │ │ └── match-path.js │ │ ├── helpers │ │ │ ├── index.js │ │ │ ├── require-authentication.js │ │ │ └── with-page.js │ │ ├── components │ │ │ ├── index.js │ │ │ ├── user-manager.js │ │ │ ├── mdx-provider.js │ │ │ ├── checkbox.js │ │ │ ├── search.js │ │ │ ├── ace.js │ │ │ └── select.js │ │ ├── pages │ │ │ ├── blog.js │ │ │ ├── login.js │ │ │ ├── 404.js │ │ │ ├── contact.js │ │ │ ├── register.js │ │ │ ├── changelog.js │ │ │ ├── index.js │ │ │ ├── login │ │ │ │ ├── github.js │ │ │ │ └── facebook.js │ │ │ ├── admin.js │ │ │ └── settings.js │ │ ├── templates │ │ │ ├── tags.js │ │ │ ├── tag.js │ │ │ ├── doc.js │ │ │ ├── contributor.js │ │ │ └── article.js │ │ ├── views │ │ │ ├── notFound.js │ │ │ ├── home.js │ │ │ ├── index.js │ │ │ ├── blog │ │ │ │ ├── tags.js │ │ │ │ ├── index.js │ │ │ │ ├── tag.js │ │ │ │ ├── contributor.js │ │ │ │ ├── articleItem.js │ │ │ │ └── article.js │ │ │ ├── admin.js │ │ │ ├── login │ │ │ │ ├── github.js │ │ │ │ └── facebook.js │ │ │ ├── changelog.js │ │ │ ├── doc │ │ │ │ ├── index.js │ │ │ │ └── navigation.js │ │ │ └── contact.js │ │ ├── server.ts │ │ ├── config │ │ │ └── index.ts │ │ ├── app.js │ │ ├── routes.js │ │ └── commands │ │ │ └── start.ts │ ├── bin │ │ ├── serverless-starter-front.cmd │ │ └── serverless-starter-front │ ├── .env │ ├── static │ │ └── images │ │ │ └── mail │ │ │ ├── header.jpg │ │ │ ├── ground_top.jpg │ │ │ └── ground_content.jpg │ ├── .gitignore │ ├── gatsby-ssr.js │ ├── gatsby-browser.js │ ├── README.md │ ├── Makefile │ ├── tsconfig.json │ ├── LICENSE.md │ └── package.json └── deploy │ ├── .env.front │ ├── composer.json │ ├── .gitignore │ ├── package.json │ ├── .env.api │ └── Makefile ├── .gitignore ├── docs ├── changelog.yaml ├── docs.yaml ├── docs │ └── index.md ├── contributors │ ├── images │ │ └── mathieu-ledru.jpg │ └── contributors.yaml └── blog │ └── 2020-08-20-welcome-serverless-starter │ ├── images │ └── cover.png │ └── index.md ├── lerna.json ├── package.json ├── .editorconfig ├── Makefile ├── LICENSE.md └── README.md /packages/api/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/api/src/Controller/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/api/src/Entity/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/api/src/Repository/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/api/translations/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # logs 2 | /*.log 3 | 4 | # npm 5 | /node_modules 6 | -------------------------------------------------------------------------------- /packages/front/src/bridges/console.js: -------------------------------------------------------------------------------- 1 | export default console 2 | -------------------------------------------------------------------------------- /packages/front/src/models/index.js: -------------------------------------------------------------------------------- 1 | export {default as Log} from './log' 2 | -------------------------------------------------------------------------------- /packages/api/config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /docs/changelog.yaml: -------------------------------------------------------------------------------- 1 | - tag: "v0.0.1" 2 | label: "Bootstrap" 3 | date: "2020-08-20" 4 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_images.scss: -------------------------------------------------------------------------------- 1 | .figure-fluid { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_section.scss: -------------------------------------------------------------------------------- 1 | .section { 2 | padding: $spacer; 3 | } 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.1" 6 | } 7 | -------------------------------------------------------------------------------- /packages/front/src/bridges/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default axios 4 | -------------------------------------------------------------------------------- /docs/docs.yaml: -------------------------------------------------------------------------------- 1 | - title: Prologue 2 | items: 3 | - title: Introduction 4 | link: /docs 5 | -------------------------------------------------------------------------------- /packages/api/config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /packages/api/config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | -------------------------------------------------------------------------------- /packages/front/src/reducers/env/actions-types.js: -------------------------------------------------------------------------------- 1 | export const COMMIT_SET_ENV = 'COMMIT_SET_ENV' 2 | -------------------------------------------------------------------------------- /packages/front/bin/serverless-starter-front.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\serverless-starter" %* 4 | -------------------------------------------------------------------------------- /packages/api/config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | ## ServerlessStarter 6 | 7 | Serverless starter 8 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_custom-forms.scss: -------------------------------------------------------------------------------- 1 | .custom-select { 2 | padding: 0; 3 | border: none; 4 | } 5 | -------------------------------------------------------------------------------- /packages/front/src/reducers/user/actions-types.js: -------------------------------------------------------------------------------- 1 | export const COMMIT_UPDATE_SETTINGS = 'COMMIT_UPDATE_SETTINGS' 2 | -------------------------------------------------------------------------------- /packages/api/README.md: -------------------------------------------------------------------------------- 1 | # serverless-starter-api 2 | 3 | This is official ServerlessStarter Api for ServerlessStarter 4 | -------------------------------------------------------------------------------- /packages/api/config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /packages/api/config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /packages/api/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /packages/api/config/routes/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | resource: . 3 | type: api_platform 4 | prefix: /api 5 | -------------------------------------------------------------------------------- /packages/api/docker-compose.override.exemple: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mysql: 4 | ports: 5 | - '3306:3306' 6 | -------------------------------------------------------------------------------- /packages/api/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/favicon.ico -------------------------------------------------------------------------------- /packages/front/src/utils/server.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getBaseUrl: () => { 3 | return process.env.GATSBY_API_URL 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /packages/api/config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /packages/api/public/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-48x48.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-72x72.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-96x96.png -------------------------------------------------------------------------------- /docs/contributors/images/mathieu-ledru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/docs/contributors/images/mathieu-ledru.jpg -------------------------------------------------------------------------------- /packages/api/config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /packages/api/public/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-144x144.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-256x256.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-384x384.png -------------------------------------------------------------------------------- /packages/api/public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/api/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /packages/front/.env: -------------------------------------------------------------------------------- 1 | PORT=8090 2 | 3 | GATSBY_URL= 4 | GATSBY_API_URL= 5 | GATSBY_FACEBOOK_APP_ID= 6 | GATSBY_GITHUB_APP_ID= 7 | GATSBY_TRACKING_ID= 8 | -------------------------------------------------------------------------------- /packages/front/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/images/logo.png -------------------------------------------------------------------------------- /packages/front/src/reducers/logs/actions-types.js: -------------------------------------------------------------------------------- 1 | export const COMMIT_ADD_LOG = 'COMMIT_ADD_LOG' 2 | export const COMMIT_READ_LOG = 'COMMIT_READ_LOG' 3 | -------------------------------------------------------------------------------- /packages/front/src/reducers/app/actions-types.js: -------------------------------------------------------------------------------- 1 | export const COMMIT_SET_PAGE = 'COMMIT_SET_PAGE' 2 | export const COMMIT_SET_THEME = 'COMMIT_SET_THEME' 3 | -------------------------------------------------------------------------------- /packages/front/static/images/mail/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/static/images/mail/header.jpg -------------------------------------------------------------------------------- /packages/deploy/.env.front: -------------------------------------------------------------------------------- 1 | PORT=8090 2 | 3 | GATSBY_URL= 4 | GATSBY_API_URL= 5 | GATSBY_FACEBOOK_APP_ID= 6 | GATSBY_GITHUB_APP_ID= 7 | GATSBY_TRACKING_ID= 8 | -------------------------------------------------------------------------------- /packages/front/static/images/mail/ground_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/static/images/mail/ground_top.jpg -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-400-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-400-Book.otf -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-600-Semi.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-600-Semi.otf -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-700-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-700-Bold.otf -------------------------------------------------------------------------------- /packages/front/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export {default as requireAuthentication} from './require-authentication' 2 | export {default as withPage} from './with-page' 3 | -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-500-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-500-Medium.otf -------------------------------------------------------------------------------- /packages/front/static/images/mail/ground_content.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/static/images/mail/ground_content.jpg -------------------------------------------------------------------------------- /packages/api/config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /packages/deploy/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "license": "MIT", 4 | "require": { 5 | "php": ">=7.2.5", 6 | "bref/bref": "^0.5.29" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-400-BookItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-400-BookItalic.otf -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-600-SemiItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-600-SemiItalic.otf -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-700-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-700-BoldItalic.otf -------------------------------------------------------------------------------- /packages/deploy/.gitignore: -------------------------------------------------------------------------------- 1 | # pack 2 | /pack 3 | 4 | # dotenv 5 | /.env.api.* 6 | /.env.front.* 7 | 8 | # serverless 9 | /.serverless 10 | 11 | # composer 12 | /vendor 13 | -------------------------------------------------------------------------------- /packages/front/src/assets/fonts/Jost-500-MediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/packages/front/src/assets/fonts/Jost-500-MediumItalic.otf -------------------------------------------------------------------------------- /packages/api/config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /docs/blog/2020-08-20-welcome-serverless-starter/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkwood-com/serverless-starter/main/docs/blog/2020-08-20-welcome-serverless-starter/images/cover.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-starter", 3 | "private": true, 4 | "homepage": "https://serverless-starter.com", 5 | "devDependencies": { 6 | "lerna": "^3.22.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | -------------------------------------------------------------------------------- /packages/api/config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | 5 | kernel: 6 | resource: ../../src/Kernel.php 7 | type: annotation 8 | -------------------------------------------------------------------------------- /packages/api/.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | -------------------------------------------------------------------------------- /packages/front/.gitignore: -------------------------------------------------------------------------------- 1 | # logs 2 | /*.log 3 | 4 | # npm 5 | /node_modules 6 | 7 | # dotenv 8 | /.env.* 9 | 10 | # gatsby 11 | /.cache 12 | /public 13 | 14 | # typescript 15 | /dist 16 | oclif.manifest.json 17 | -------------------------------------------------------------------------------- /docs/contributors/contributors.yaml: -------------------------------------------------------------------------------- 1 | - name: Mathieu Ledru 2 | description: >- 3 | Hello I'm Mathieu ! I am the creator of ServerlessStarter. 4 | image: images/mathieu-ledru.jpg 5 | location: "Paris, France" 6 | twitter: "@matyo91" 7 | -------------------------------------------------------------------------------- /packages/api/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mysql: 4 | container_name: serverless-starter_mysql 5 | hostname: serverless-starter_mysql 6 | image: mysql:5.7 7 | environment: 8 | - MYSQL_ROOT_PASSWORD=root 9 | -------------------------------------------------------------------------------- /packages/front/src/utils/create-store.js: -------------------------------------------------------------------------------- 1 | import {createStore, applyMiddleware} from 'redux' 2 | import thunk from 'redux-thunk' 3 | 4 | export default (reducer, preloadedState) => { 5 | return createStore(reducer, preloadedState, applyMiddleware(thunk)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/front/src/utils/index.js: -------------------------------------------------------------------------------- 1 | export {default as createStore} from './create-store' 2 | export {default as server} from './server' 3 | export {default as matchPath} from './match-path' 4 | export {default as copyTextToClipboard} from './copy-text-to-clipboard' 5 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/utilities.scss: -------------------------------------------------------------------------------- 1 | .border-top { 2 | border-top-color: var(--border-color) !important; 3 | } 4 | 5 | @each $color, $value in $theme-colors { 6 | .text-#{$color} { 7 | color: var(--#{$color}-color) !important; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/front/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | export {default as app} from './app/index' 2 | export {default as auth} from './auth/index' 3 | export {default as env} from './env/index' 4 | export {default as logs} from './logs/index' 5 | export {default as user} from './user/index' 6 | -------------------------------------------------------------------------------- /packages/api/config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /packages/api/config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /packages/front/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Layout from './src/layouts' 3 | import App from './src/app' 4 | 5 | export const wrapPageElement = ({element, props}) => { 6 | return {element} 7 | } 8 | export const wrapRootElement = App 9 | -------------------------------------------------------------------------------- /packages/api/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | -------------------------------------------------------------------------------- /packages/front/src/reducers/env/actions.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_SET_ENV} from './actions-types' 2 | 3 | export const commitSetEnv = env => { 4 | return dispatch => { 5 | dispatch({ 6 | type: COMMIT_SET_ENV, 7 | env, 8 | }) 9 | return Promise.resolve() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | 3 | $font-family-base: Jost, sans-serif; 4 | $font-size-base: 1.125rem; 5 | 6 | // Forms 7 | 8 | $custom-control-indicator-size: 2rem; 9 | 10 | // Navbar 11 | 12 | $navbar-padding-y: 0rem; 13 | $navbar-padding-x: 0rem; 14 | -------------------------------------------------------------------------------- /packages/api/config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /packages/front/src/reducers/auth/actions-types.js: -------------------------------------------------------------------------------- 1 | export const COMMIT_LOGIN_USER_REQUEST = 'COMMIT_LOGIN_USER_REQUEST' 2 | export const COMMIT_LOGIN_USER_SUCCESS = 'COMMIT_LOGIN_USER_SUCCESS' 3 | export const COMMIT_LOGIN_USER_FAILURE = 'COMMIT_LOGIN_USER_FAILURE' 4 | export const COMMIT_LOGOUT_USER = 'COMMIT_LOGOUT_USER' 5 | -------------------------------------------------------------------------------- /packages/front/src/components/index.js: -------------------------------------------------------------------------------- 1 | export {default as Ace} from './ace' 2 | export {default as Checkbox} from './checkbox' 3 | export {default as Select} from './select' 4 | export {default as Search} from './search' 5 | export {default as MDXProvider} from './mdx-provider' 6 | export {default as UserManager} from './user-manager' 7 | -------------------------------------------------------------------------------- /packages/front/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import "./src/assets/styles/index.scss" 2 | import React from "react" 3 | import Layout from './src/layouts' 4 | import App from './src/app' 5 | 6 | export const wrapPageElement = ({element, props}) => { 7 | return {element} 8 | } 9 | export const wrapRootElement = App 10 | -------------------------------------------------------------------------------- /packages/front/src/pages/blog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Blog} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const BlogPage = withPage(Blog, 'blog', { 7 | location: location, 8 | title: 'Blog', 9 | description: 'Blog', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/pages/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Login} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const LoginPage = withPage(Login, 'login', { 7 | location: location, 8 | title: 'Login', 9 | description: 'Login', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {NotFound} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const NotFoundPage = withPage(NotFound, '404', { 7 | location: location, 8 | title: '404', 9 | description: '404', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/badge.scss: -------------------------------------------------------------------------------- 1 | @each $color, $value in $theme-colors { 2 | .badge-#{$color} { 3 | background-color: var(--#{$color}-color); 4 | border-color: var(--#{$color}-color); 5 | 6 | @include hover { 7 | background-color: var(--#{$color}-hover-color); 8 | border-color: var(--#{$color}-hover-color); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/front/README.md: -------------------------------------------------------------------------------- 1 | # front 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /packages/front/src/pages/contact.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Contact} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const ContactPage = withPage(Contact, 'contact', { 7 | location: location, 8 | title: 'Contact', 9 | description: 'Contact', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" 9 | -------------------------------------------------------------------------------- /packages/front/src/pages/register.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Register} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const RegisterPage = withPage(Register, 'register', { 7 | location: location, 8 | title: 'Register', 9 | description: 'Register', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/templates/tags.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Tags} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location, pageContext: {tags}}) => { 6 | const TagsPage = withPage(Tags, 'tags', { 7 | location: location, 8 | title: 'Tags', 9 | description: 'Tags', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/pages/changelog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Changelog} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const ChangelogPage = withPage(Changelog, 'changelog', { 7 | location: location, 8 | title: 'ChangeLog', 9 | description: 'ChangeLog', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Home} from '../views' 3 | import {withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const HomePage = withPage(Home, 'home', { 7 | location: location, 8 | title: 'ServerlessStarter', 9 | description: 'Unified Workflow Automation Tool', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/utils/copy-text-to-clipboard.js: -------------------------------------------------------------------------------- 1 | export default text => { 2 | let textArea = document.createElement('textarea') 3 | textArea.value = text 4 | document.body.appendChild(textArea) 5 | textArea.focus() 6 | textArea.select() 7 | 8 | try { 9 | document.execCommand('copy') 10 | } catch (err) { 11 | } 12 | 13 | document.body.removeChild(textArea) 14 | } 15 | -------------------------------------------------------------------------------- /packages/api/config/packages/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | title: ServerlessStarter API 3 | version: 0.0.1 4 | mapping: 5 | paths: ['%kernel.project_dir%/src/Entity'] 6 | patch_formats: 7 | json: ['application/merge-patch+json'] 8 | swagger: 9 | versions: [3] 10 | api_keys: 11 | apiKey: 12 | name: ServerlessStarter-Authorization 13 | type: header 14 | -------------------------------------------------------------------------------- /packages/api/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__) . '/.env'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/config/packages/nelmio_cors.yaml: -------------------------------------------------------------------------------- 1 | nelmio_cors: 2 | defaults: 3 | origin_regex: true 4 | allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] 5 | allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 6 | allow_headers: ['Content-Type', 'Authorization', 'ServerlessStarter-Authorization'] 7 | expose_headers: ['Link'] 8 | max_age: 3600 9 | paths: 10 | '^/': null 11 | -------------------------------------------------------------------------------- /packages/front/src/views/notFound.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | export default class NotFound extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 |
9 |

404

10 |
11 |
12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [Makefile] 12 | indent_style = tab 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | max_line_length = 80 17 | 18 | [*.{yml, yaml, js, ts, scss, json, css}] 19 | indent_size = 2 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /packages/front/src/models/log.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export default class Log { 4 | static get FEED_CREATE_FAIL() { 5 | return 1 6 | } 7 | 8 | static get FEED_DATA_SET_FAIL() { 9 | return 2 10 | } 11 | 12 | static get USER_LOGIN_FAIL() { 13 | return 3 14 | } 15 | 16 | constructor(data) { 17 | Object.assign(this, data) 18 | 19 | this.created = moment(this.created) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/front/src/pages/login/github.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {GithubLogin} from '../../views' 3 | import {withPage} from '../../helpers' 4 | 5 | export default ({location}) => { 6 | const GithubLoginPage = withPage(GithubLogin, 'github-login', { 7 | location: location, 8 | title: 'Login Github', 9 | description: 'Login Github', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/src/DataFixtures/AppFixtures.php: -------------------------------------------------------------------------------- 1 | persist($product); 14 | 15 | $manager->flush(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/front/src/pages/login/facebook.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {FacebookLogin} from '../../views' 3 | import {withPage} from '../../helpers' 4 | 5 | export default ({location}) => { 6 | const FacebookLoginPage = withPage(FacebookLogin, 'facebook-login', { 7 | location: location, 8 | title: 'Login Facebook', 9 | description: 'Login Facebook', 10 | }) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /packages/front/src/reducers/contact/actions.js: -------------------------------------------------------------------------------- 1 | import request from 'axios' 2 | import server from '../../utils/server' 3 | 4 | export const contact = (email, message) => { 5 | return dispatch => { 6 | return request 7 | .post(`${server.getBaseUrl()}/api/contact/create`, { 8 | email: email, 9 | message: message, 10 | }) 11 | .then(response => { 12 | return response.data 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/blog/2020-08-20-welcome-serverless-starter/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Welcome ServerlessStarter ! 3 | date: 2020-08-20 4 | author: Mathieu Ledru 5 | cover: images/cover.png 6 | coverSeo: images/cover.png 7 | coverAuthor: Darkwood 8 | coverOriginalUrl: https://darkwood.fr 9 | tags: ["blog"] 10 | --- 11 | 12 | Serverless starter 13 | 14 | It’s a great pleasure to announce public and release of [serverless-starter.com](https://serverless-starter.com) 15 | -------------------------------------------------------------------------------- /packages/front/src/pages/admin.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Admin} from '../views' 3 | import {requireAuthentication, withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const AdminPage = withPage(Admin, 'admin', { 7 | location: location, 8 | title: 'Admin', 9 | description: 'Admin', 10 | }) 11 | const AuthAdminPage = requireAuthentication(AdminPage, 'ROLE_SUPER_ADMIN') 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /packages/front/src/reducers/env/index.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_SET_ENV} from './actions-types' 2 | 3 | const defaultState = { 4 | facebookAppId: null, 5 | githubAppId: null, 6 | } 7 | 8 | const env = (state = defaultState, action) => { 9 | switch (action.type) { 10 | case COMMIT_SET_ENV: 11 | return { 12 | ...state, 13 | ...action.env, 14 | } 15 | default: 16 | return state 17 | } 18 | } 19 | 20 | export default env 21 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/reboot.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--text-color); 3 | background-color: var(--bg-color); 4 | } 5 | 6 | a { 7 | color: var(--primary-color); 8 | 9 | @include hover() { 10 | color: var(--primary-hover-color); 11 | } 12 | } 13 | 14 | input { 15 | background-color: var(--input-color); 16 | border-color: var(--border-color); 17 | 18 | :focus { 19 | box-shadow: inset 0 0 0 1px var(--primary-color); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/api/templates/mails/contact.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "mails/layout.html.twig" %} 2 | {% block subject %}[ServerlessStarter] Contact{% endblock %} 3 | 4 | {% block content %} 5 | Bonjour Mathieu !

6 | 7 | Une nouvelle demande de contact sur le site ServerlessStarter :

8 | email : {{ contact.email }}
9 | contenu : {{ contact.message }}
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_card.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | background-color: var(--bg-color); 3 | border: 1px solid var(--border-color); 4 | } 5 | 6 | .card-link { 7 | a { 8 | position: relative; 9 | z-index: 1; 10 | } 11 | 12 | > a { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | opacity: 0; 19 | overflow: hidden; 20 | text-indent: -9999px; 21 | z-index: 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/front/src/pages/settings.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Settings} from '../views' 3 | import {requireAuthentication, withPage} from '../helpers' 4 | 5 | export default ({location}) => { 6 | const AuthSettings = withPage(Settings, 'settings', { 7 | location: location, 8 | title: 'Settings', 9 | description: 'Settings', 10 | }) 11 | const AuthSettingsPage = requireAuthentication(AuthSettings) 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /packages/front/src/views/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Home = () => { 4 | return ( 5 | <> 6 |
7 |
8 |
9 |

ServerlessStarter

10 |

Serverless starter demo

11 |
12 |
13 |
14 | 15 | ) 16 | } 17 | 18 | export default Home 19 | -------------------------------------------------------------------------------- /packages/api/bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | render('base.html.twig'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ##usage : 3 | ##------- 4 | 5 | LERNA = node_modules/.bin/lerna 6 | 7 | clean: ## clean 8 | $(LERNA) run clean; 9 | $(LERNA) clean -y; 10 | rm -rf node_modules 11 | 12 | install: ## install 13 | npm install 14 | $(LERNA) bootstrap --hoist --no-ci 15 | 16 | # DEFAULT 17 | .DEFAULT_GOAL := help 18 | help: 19 | @grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' 20 | 21 | ## 22 | -------------------------------------------------------------------------------- /packages/api/config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | nested: 10 | type: stream 11 | path: "php://stderr" 12 | level: debug 13 | console: 14 | type: console 15 | process_psr_3_messages: false 16 | channels: ["!event", "!doctrine"] 17 | -------------------------------------------------------------------------------- /packages/front/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import config from './config'; 3 | 4 | export default async function server() { 5 | const PORT = config.get('port'); 6 | 7 | const app = express(); 8 | app.use('/', express.static('./public')); 9 | 10 | return new Promise((resolve => { 11 | app.listen(PORT, (err) => { 12 | if (err) { 13 | console.log(err); 14 | process.exit(1); 15 | return; 16 | } 17 | 18 | resolve(app) 19 | }); 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /packages/api/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | /docker-compose.override.yml 11 | ###< symfony/framework-bundle ### 12 | 13 | ###> symfony/phpunit-bridge ### 14 | .phpunit 15 | .phpunit.result.cache 16 | /phpunit.xml 17 | ###< symfony/phpunit-bridge ### 18 | 19 | ###> lexik/jwt-authentication-bundle ### 20 | /config/jwt/*.pem 21 | ###< lexik/jwt-authentication-bundle ### 22 | -------------------------------------------------------------------------------- /packages/api/config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | #chatter_transports: 4 | # slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: ['email'] 12 | high: ['email'] 13 | medium: ['email'] 14 | low: ['email'] 15 | admin_recipients: 16 | - { email: admin@example.com } 17 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/themes/_dark.scss: -------------------------------------------------------------------------------- 1 | $app-themes: () !default; 2 | $app-themes: map-merge( 3 | ( 4 | "dark": ( 5 | "text": #EBF4F1, 6 | "bg": #091A28, 7 | "input": #0E2233, 8 | "border": #0F2B3D, 9 | "primary": #0066CC, 10 | "secondary": #576068, 11 | "success": #009924, 12 | "danger": #99000F, 13 | "warning": #997300, 14 | "info": #008599, 15 | "light": #bdbdbd, 16 | "dark": #1A1452, 17 | ), 18 | ), 19 | $app-themes 20 | ); 21 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/themes/_light.scss: -------------------------------------------------------------------------------- 1 | $app-themes: () !default; 2 | $app-themes: map-merge( 3 | ( 4 | "light": ( 5 | "text": #000000, 6 | "bg": #FFFFFF, 7 | "input": #FFFFFF, 8 | "border": #EFF2F7, 9 | "primary": #007bff, 10 | "secondary": #6c757d, 11 | "success": #28a745, 12 | "danger": #dc3545, 13 | "warning": #ffc107, 14 | "info": #17a2b8, 15 | "light": #e9ecef, 16 | "dark": #1A1452, 17 | ), 18 | ), 19 | $app-themes 20 | ); 21 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/themes/_sepia.scss: -------------------------------------------------------------------------------- 1 | $app-themes: () !default; 2 | $app-themes: map-merge( 3 | ( 4 | "sepia": ( 5 | "text": #433422, 6 | "bg": #F1E7D0, 7 | "input": #EADEC2, 8 | "border": #DED0BF, 9 | "primary": #0066CC, 10 | "secondary": #876944, 11 | "success": #00996B, 12 | "danger": #99000F, 13 | "warning": #997300, 14 | "info": #008599, 15 | "light": #EADEC2, 16 | "dark": #1A1452, 17 | ), 18 | ), 19 | $app-themes 20 | ); 21 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | // bootstrap 2 | @import "variables"; 3 | @import "bootstrap/scss/bootstrap"; 4 | 5 | // bootstrap override 6 | @import "reboot"; 7 | @import "fonts"; 8 | @import "images"; 9 | @import "forms"; 10 | @import "buttons"; 11 | @import "custom-forms"; 12 | @import "nav"; 13 | @import "navbar"; 14 | @import "card"; 15 | @import "badge"; 16 | @import "utilities"; 17 | 18 | // components 19 | @import "themes"; 20 | @import "sidebar"; 21 | @import "section"; 22 | @import "timeline"; 23 | @import "user"; 24 | @import "app"; 25 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_themes.scss: -------------------------------------------------------------------------------- 1 | $app-themes: (); 2 | 3 | @import "themes/light"; 4 | @import "themes/dark"; 5 | @import "themes/sepia"; 6 | 7 | @each $app-theme, $app-colors in $app-themes { 8 | body.theme-#{$app-theme} { 9 | @each $color, $value in $app-colors { 10 | --#{$color}-color: #{$value}; 11 | } 12 | 13 | --navbar-top-color: #{rgba(map-get($app-colors, 'bg'), 0.8)}; 14 | 15 | @each $color, $value in $theme-colors { 16 | --#{$color}-hover-color: #{darken(map-get($app-colors, $color), 15%)}; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/api/config/packages/lexik_jwt_authentication.yaml: -------------------------------------------------------------------------------- 1 | lexik_jwt_authentication: 2 | secret_key: '%env(resolve:JWT_SECRET_KEY)%' 3 | public_key: '%env(resolve:JWT_PUBLIC_KEY)%' 4 | pass_phrase: '%env(JWT_PASSPHRASE)%' 5 | token_ttl: 86400 # 24 hours 6 | user_identity_field: email 7 | token_extractors: 8 | # look for a token as Authorization Header 9 | authorization_header: 10 | enabled: true 11 | prefix: Bearer 12 | name: ServerlessStarter-Authorization 13 | query_parameter: 14 | enabled: true 15 | name: bearer 16 | -------------------------------------------------------------------------------- /packages/front/src/reducers/user/index.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_UPDATE_SETTINGS} from './actions-types' 2 | 3 | const defaultState = { 4 | apiKey: null, 5 | username: null, 6 | firstName: null, 7 | lastName: null, 8 | facebookId: null, 9 | githubId: null, 10 | roles: [], 11 | } 12 | 13 | const user = (state = defaultState, action) => { 14 | switch (action.type) { 15 | case COMMIT_UPDATE_SETTINGS: 16 | return { 17 | ...state, 18 | ...action.user, 19 | } 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | export default user 26 | -------------------------------------------------------------------------------- /packages/api/config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /packages/front/src/reducers/app/index.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_SET_PAGE, COMMIT_SET_THEME} from './actions-types' 2 | 3 | const defaultState = { 4 | page: 'home', 5 | theme: 'light', 6 | } 7 | 8 | const app = (state = defaultState, action) => { 9 | switch (action.type) { 10 | case COMMIT_SET_PAGE: 11 | return { 12 | ...state, 13 | page: action.page, 14 | } 15 | case COMMIT_SET_THEME: 16 | return { 17 | ...state, 18 | theme: action.theme, 19 | } 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | export default app 26 | -------------------------------------------------------------------------------- /packages/api/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | #http_method_override: true 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: null 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | 14 | #esi: true 15 | #fragments: true 16 | php_errors: 17 | log: true 18 | -------------------------------------------------------------------------------- /packages/api/config/packages/doctrine_extensions.yaml: -------------------------------------------------------------------------------- 1 | # services to handle doctrine extensions 2 | services: 3 | gedmo.listener.timestampable: 4 | class: Gedmo\Timestampable\TimestampableListener 5 | tags: 6 | - { name: doctrine.event_subscriber, connection: default } 7 | calls: 8 | - [ setAnnotationReader, [ "@annotation_reader" ] ] 9 | 10 | gedmo.listener.sluggable: 11 | class: Gedmo\Sluggable\SluggableListener 12 | tags: 13 | - { name: doctrine.event_subscriber, connection: default } 14 | calls: 15 | - [ setAnnotationReader, [ "@annotation_reader" ] ] 16 | -------------------------------------------------------------------------------- /packages/api/config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /packages/api/src/Entity/Config.php: -------------------------------------------------------------------------------- 1 | id; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_app.scss: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | padding-bottom: 60px; 4 | 5 | @include media-breakpoint-up(md) { 6 | padding-bottom: 0; 7 | } 8 | } 9 | 10 | #home { 11 | padding-top: 0px; 12 | 13 | .navbar-top { 14 | border-bottom: 0; 15 | } 16 | 17 | .navbar-nav { 18 | .nav-link { 19 | &.active { 20 | border-bottom: 0 !important; 21 | } 22 | } 23 | } 24 | } 25 | 26 | #gatsby-noscript { 27 | display: block; 28 | position: relative; 29 | color: #0c5460; 30 | background-color: #d1ecf1; 31 | border-color: #bee5eb; 32 | padding: .75rem 1.25rem; 33 | text-align: center; 34 | } 35 | -------------------------------------------------------------------------------- /packages/front/src/config/index.ts: -------------------------------------------------------------------------------- 1 | import * as convict from 'convict'; 2 | import * as dotenv from 'dotenv'; 3 | 4 | const activeEnv = process.env.NODE_ENV || 'development'; 5 | 6 | const envFound = dotenv.config({ 7 | path: `.env.${activeEnv}`, 8 | }); 9 | if (!envFound) { 10 | // Throw generic error 11 | throw new Error(`Couldn't find .env.${activeEnv} file`); 12 | } 13 | 14 | const config = convict({ 15 | port: { 16 | format: Number, 17 | default: 8090, 18 | arg: 'port', 19 | env: 'PORT', 20 | doc: 'HTTP port serverless-starter api can be reached' 21 | }, 22 | }); 23 | 24 | config.validate({ 25 | allowed: 'strict', 26 | }); 27 | 28 | export default config 29 | -------------------------------------------------------------------------------- /packages/api/config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(resolve:DATABASE_URL)%' 4 | 5 | # configure these for your database server 6 | driver: 'pdo_mysql' 7 | server_version: '5.7' 8 | charset: utf8mb4 9 | default_table_options: 10 | charset: utf8mb4 11 | collate: utf8mb4_unicode_ci 12 | orm: 13 | auto_generate_proxy_classes: true 14 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 15 | auto_mapping: true 16 | mappings: 17 | App: 18 | is_bundle: false 19 | type: annotation 20 | dir: '%kernel.project_dir%/src/Entity' 21 | prefix: 'App\Entity' 22 | alias: App 23 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_timeline.scss: -------------------------------------------------------------------------------- 1 | ul.timeline { 2 | list-style-type: none; 3 | position: relative; 4 | } 5 | 6 | ul.timeline:before { 7 | content: ' '; 8 | background: var(--secondary-color); 9 | display: inline-block; 10 | position: absolute; 11 | left: 29px; 12 | width: 2px; 13 | height: 100%; 14 | } 15 | 16 | ul.timeline > li { 17 | margin: 20px 0; 18 | padding-left: 20px; 19 | } 20 | 21 | ul.timeline > li:before { 22 | content: ' '; 23 | background: var(--bg-color); 24 | display: inline-block; 25 | position: absolute; 26 | border-radius: 50%; 27 | border: 3px solid var(--primary-color); 28 | left: 20px; 29 | width: 20px; 30 | height: 20px; 31 | } 32 | -------------------------------------------------------------------------------- /packages/api/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /packages/front/src/reducers/logs/actions.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_ADD_LOG, COMMIT_READ_LOG} from './actions-types' 2 | 3 | export const getNewLogs = state => { 4 | return Object.keys(state).reduce((newLogs, key) => { 5 | if (state[key].status === 'new') { 6 | newLogs[key] = state[key] 7 | } 8 | return newLogs 9 | }, {}) 10 | } 11 | 12 | export const commitAddLog = message => { 13 | return dispatch => { 14 | dispatch({ 15 | type: COMMIT_ADD_LOG, 16 | message, 17 | }) 18 | return Promise.resolve() 19 | } 20 | } 21 | export const commitReadLog = id => { 22 | return dispatch => { 23 | dispatch({ 24 | type: COMMIT_READ_LOG, 25 | id, 26 | }) 27 | return Promise.resolve() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/front/Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ##usage : 3 | ##------- 4 | 5 | NODE_ENV ?= local 6 | NPM_BIN = ./node_modules/.bin 7 | 8 | build: clean ## build 9 | if [ ! -f .env.$(NODE_ENV) ]; then cp .env .env.$(NODE_ENV); fi 10 | #$(NPM_BIN)/tsc -b 11 | #$(NPM_BIN)/oclif-dev manifest 12 | GATSBY_ACTIVE_ENV=$(NODE_ENV) $(NPM_BIN)/gatsby build 13 | 14 | clean: ## clean 15 | rm -rf .cache dist public 16 | 17 | dev: ## dev 18 | GATSBY_ACTIVE_ENV=development $(NPM_BIN)/gatsby develop -p 8090 --https 19 | 20 | serve: build ## serve 21 | ./bin/serverless-starter-front 22 | 23 | # DEFAULT 24 | .DEFAULT_GOAL := help 25 | help: 26 | @grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' 27 | 28 | ## 29 | -------------------------------------------------------------------------------- /packages/api/src/Form/ConfigType.php: -------------------------------------------------------------------------------- 1 | setDefaults([ 26 | 'data_class' => Config::class, 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/front/src/views/index.js: -------------------------------------------------------------------------------- 1 | export {default as Admin} from './admin' 2 | export {default as Article} from './blog/article' 3 | export {default as Blog} from './blog' 4 | export {default as Contact} from './contact' 5 | export {default as Contributor} from './blog/contributor' 6 | export {default as Changelog} from './changelog' 7 | export {default as Doc} from './doc' 8 | export {default as Home} from './home' 9 | export {default as Login} from './login' 10 | export {default as FacebookLogin} from './login/facebook' 11 | export {default as GithubLogin} from './login/github' 12 | export {default as NotFound} from './notFound' 13 | export {default as Register} from './login/register' 14 | export {default as Settings} from './settings' 15 | export {default as Tag} from './blog/tag' 16 | export {default as Tags} from './blog/tags' 17 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless-starter-com/serverless-starter-api", 3 | "version": "0.0.1", 4 | "description": "ServerlessStarter api", 5 | "author": { 6 | "name": "Mathieu Ledru", 7 | "email": "matyo91@gmail.com" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/darkwood-fr/serverless-starter/issues" 11 | }, 12 | "homepage": "https://github.com/darkwood-fr/serverless-starter/tree/master/library/serverless-starter-api#readme", 13 | "keywords": [ 14 | "serverless-starter", 15 | "serverless-starter-api" 16 | ], 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/darkwood-fr/serverless-starter.git", 21 | "directory": "library/serverless-starter-api" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /packages/api/src/Security/Voter/ProgramEditVoter.php: -------------------------------------------------------------------------------- 1 | getUser() === $subject->getUser(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/deploy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless-starter-com/serverless-starter-deploy", 3 | "version": "0.0.1", 4 | "description": "ServerlessStarter deploy", 5 | "author": { 6 | "name": "Mathieu Ledru", 7 | "email": "matyo91@gmail.com" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/darkwood-fr/serverless-starter/issues" 11 | }, 12 | "homepage": "https://github.com/darkwood-fr/serverless-starter/tree/master/library/serverless-starter-deploy#readme", 13 | "keywords": [ 14 | "serverless-starter", 15 | "serverless-starter-deploy" 16 | ], 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/darkwood-fr/serverless-starter.git", 21 | "directory": "library/serverless-starter-deploy" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es2017" 5 | ], 6 | "types": [ 7 | "node" 8 | ], 9 | "module": "commonjs", 10 | "noImplicitAny": true, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "preserveConstEnums": true, 16 | "declaration": true, 17 | "outDir": "./dist", 18 | "target": "es2017", 19 | "sourceMap": true, 20 | "emitDecoratorMetadata": true, 21 | "experimentalDecorators": true, 22 | "typeRoots": [ 23 | "./src/types", 24 | "./node_modules/@types" 25 | ] 26 | }, 27 | "include": [ 28 | "./src/**/*" 29 | ], 30 | "exclude": [ 31 | "./dist/**/*", 32 | "./node_modules/**/*", 33 | "./**/*.spec.ts" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_nav.scss: -------------------------------------------------------------------------------- 1 | .nav-bottom { 2 | .nav-link { 3 | color: var(--secondary-color); 4 | 5 | @include hover-focus { 6 | color: var(--secondary-hover-color); 7 | } 8 | } 9 | } 10 | 11 | .nav-bar-bottom { 12 | border-top: $border-width solid var(--border-color); 13 | background-color: var(--bg-color) !important; 14 | 15 | .nav-link { 16 | margin: 0; 17 | padding-top: $spacer; 18 | padding-bottom: $spacer; 19 | color: var(--secondary-color); 20 | 21 | @include hover-focus { 22 | color: var(--secondary-hover-color); 23 | } 24 | 25 | &.active { 26 | color: var(--primary-color); 27 | border-top: $border-width solid var(--primary-color) !important; 28 | 29 | @include hover-focus { 30 | color: var(--primary-hover-color) !important; 31 | } 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /packages/front/src/templates/tag.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Tag} from '../views' 3 | import {graphql} from 'gatsby' 4 | import {withPage} from '../helpers' 5 | 6 | export default ({data, location, pageContext: {tag}}) => { 7 | const {articles} = data 8 | 9 | const TagPage = withPage(Tag, 'tag', { 10 | location: location, 11 | title: tag, 12 | description: tag, 13 | }) 14 | 15 | return 16 | } 17 | 18 | export const query = graphql` 19 | query($tag: String) { 20 | articles: allMdx( 21 | filter: { 22 | fields: { sourceName: { eq: "blog" } } 23 | frontmatter: { tags: { eq: $tag } } 24 | } 25 | sort: { fields: frontmatter___date, order: DESC } 26 | ) { 27 | edges { 28 | node { 29 | ...ArticleItemFragment 30 | } 31 | } 32 | } 33 | } 34 | ` 35 | -------------------------------------------------------------------------------- /packages/api/src/Repository/ConfigRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 19 | ->select('c'); 20 | 21 | if ($id) { 22 | $qb->andWhere('c.id = :id')->setParameter('id', $id); 23 | } else { 24 | $qb->setMaxResults(1); 25 | } 26 | 27 | $query = $qb->getQuery(); 28 | 29 | return $query->getOneOrNullResult(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/api/src/Repository/ContactRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 19 | ->select('c'); 20 | 21 | if ($id) { 22 | $qb->andWhere('c.id = :id')->setParameter('id', $id); 23 | } else { 24 | $qb->setMaxResults(1); 25 | } 26 | 27 | $query = $qb->getQuery(); 28 | 29 | return $query->getOneOrNullResult(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/front/src/reducers/logs/index.js: -------------------------------------------------------------------------------- 1 | import {Log} from '../../models' 2 | import {COMMIT_ADD_LOG, COMMIT_READ_LOG} from './actions-types' 3 | 4 | let id = 1 5 | const defaultState = {} 6 | 7 | const logs = (state = defaultState, action) => { 8 | switch (action.type) { 9 | case COMMIT_ADD_LOG: 10 | let item = new Log({ 11 | id: id, 12 | message: action.message, 13 | status: 'new', 14 | }) 15 | id++ 16 | 17 | state[item.id] = item 18 | return { 19 | ...state, 20 | } 21 | case COMMIT_READ_LOG: 22 | return Object.keys(state).map(key => { 23 | let item = state[key] 24 | if (item.id !== action.id) { 25 | return item 26 | } 27 | 28 | return { 29 | ...item, 30 | ...{status: 'read'}, 31 | } 32 | }) 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | export default logs 39 | -------------------------------------------------------------------------------- /packages/api/src/Security/Voter/ProgramViewVoter.php: -------------------------------------------------------------------------------- 1 | getPublic()) { 30 | return true; 31 | } 32 | 33 | return $token->getUser() === $subject->getUser(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_forms.scss: -------------------------------------------------------------------------------- 1 | .form-control { 2 | background-color: var(--input-color); 3 | border-color: var(--border-color); 4 | 5 | &:focus { 6 | background-color: var(--input-color); 7 | border-color: var(--border-color); 8 | } 9 | } 10 | 11 | .form-sm-horizontal { 12 | @include media-breakpoint-up(sm) { 13 | .form-group .col-form-label:first-child { 14 | text-align: right; 15 | } 16 | } 17 | } 18 | 19 | .custom-select { 20 | background-color: var(--bg-color); 21 | } 22 | 23 | .custom-control-label::before { 24 | background-color: var(--secondary-color); 25 | border-color: var(--border-color); 26 | } 27 | 28 | .custom-control-input:checked ~ .custom-control-label::before { 29 | background-color: var(--primary-color); 30 | border-color: var(--border-color); 31 | } 32 | 33 | .input-group-text { 34 | background-color: var(--border-color); 35 | border-color: var(--border-color); 36 | color: var(--text-color); 37 | } 38 | -------------------------------------------------------------------------------- /packages/api/public/index.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__) . '/.env'); 11 | 12 | if ($_SERVER['APP_DEBUG']) { 13 | umask(0000); 14 | 15 | Debug::enable(); 16 | } 17 | 18 | if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) { 19 | Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); 20 | } 21 | 22 | if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) { 23 | Request::setTrustedHosts([$trustedHosts]); 24 | } 25 | 26 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']); 27 | $request = Request::createFromGlobals(); 28 | $response = $kernel->handle($request); 29 | $response->send(); 30 | $kernel->terminate($request, $response); 31 | -------------------------------------------------------------------------------- /packages/front/src/components/user-manager.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {connect} from 'react-redux' 3 | import {fetchSettings} from '../reducers/user/actions' 4 | 5 | class UserManager extends Component { 6 | state = { 7 | fetching: false, 8 | } 9 | 10 | componentDidMount() { 11 | const {auth} = this.props 12 | 13 | if (auth.isAuthenticated) { 14 | this.onFetchUser(auth.token) 15 | } 16 | } 17 | 18 | componentDidUpdate(prevProps) { 19 | const {auth} = this.props 20 | 21 | if (auth.token !== prevProps.auth.token && auth.isAuthenticated) { 22 | this.onFetchUser(auth.token) 23 | } 24 | } 25 | 26 | onFetchUser = token => { 27 | Promise.all([ 28 | this.props.dispatch(fetchSettings(token)), 29 | ]) 30 | } 31 | 32 | render() { 33 | return <> 34 | } 35 | } 36 | 37 | export default connect(state => { 38 | return { 39 | auth: state.auth, 40 | user: state.user, 41 | } 42 | })(UserManager) 43 | -------------------------------------------------------------------------------- /packages/api/templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | ServerlessStarter Api 11 | 12 | 13 |
14 |
15 | ServerlessStarter API 16 |

ServerlessStarter API

17 |

Get documentation

18 |

From https://serverless-starter.com

19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/front/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {combineReducers} from 'redux' 3 | import {Provider} from 'react-redux' 4 | import {app, auth, env, logs, user} from './reducers' 5 | import {createStore} from './utils' 6 | import {commitLoginUserSuccess} from './reducers/auth/actions' 7 | import {commitSetEnv} from './reducers/env/actions' 8 | 9 | let store = createStore( 10 | combineReducers({ 11 | app, 12 | auth, 13 | env, 14 | logs, 15 | user, 16 | }) 17 | ) 18 | 19 | const ENV = { 20 | url: process.env.GATSBY_URL, 21 | apiUrl: process.env.GATSBY_API_URL, 22 | facebookAppId: process.env.GATSBY_FACEBOOK_APP_ID, 23 | githubAppId: process.env.GATSBY_GITHUB_APP_ID, 24 | } 25 | store.dispatch(commitSetEnv(ENV)) 26 | 27 | if (typeof window !== `undefined`) { 28 | let token = window.localStorage.getItem('token') 29 | if (token !== null) { 30 | store.dispatch(commitLoginUserSuccess(token)) 31 | } 32 | } 33 | 34 | export default ({element}) => {element} 35 | -------------------------------------------------------------------------------- /packages/api/src/Form/ContactType.php: -------------------------------------------------------------------------------- 1 | add('email', EmailType::class); 24 | $builder->add('message', TextareaType::class); 25 | } 26 | 27 | public function configureOptions(OptionsResolver $resolver) 28 | { 29 | $resolver->setDefaults([ 30 | 'data_class' => Contact::class, 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/front/src/templates/doc.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Doc} from '../views' 3 | import {graphql} from 'gatsby' 4 | import {withPage} from '../helpers' 5 | 6 | export default ({data, location, pageContext: {previous, next}}) => { 7 | const {doc, docNav} = data 8 | 9 | const DocPage = withPage(Doc, 'doc', { 10 | location: location, 11 | title: doc.frontmatter.title, 12 | description: doc.excerpt, 13 | }) 14 | 15 | return 16 | } 17 | 18 | export const query = graphql` 19 | query($id: String) { 20 | doc: mdx(id: { eq: $id }) { 21 | excerpt 22 | body 23 | frontmatter { 24 | title 25 | } 26 | fields { 27 | slug 28 | } 29 | parent { 30 | ... on File { 31 | relativePath 32 | } 33 | } 34 | } 35 | docNav: allDocsYaml { 36 | nodes { 37 | title 38 | items { 39 | link 40 | title 41 | } 42 | } 43 | } 44 | } 45 | ` 46 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | padding: 0 $spacer; 3 | } 4 | 5 | .navbar-top { 6 | z-index: 20; 7 | top: 0; 8 | right: 0; 9 | left: 0; 10 | background-color: var(--navbar-top-color); 11 | border-bottom: 1px solid var(--border-color); 12 | flex-wrap: nowrap; 13 | position: fixed; 14 | transition: background-color .3s, border-color, .3s; 15 | -webkit-backdrop-filter: blur(4px); 16 | backdrop-filter: blur(4px); 17 | } 18 | 19 | .navbar-nav { 20 | .nav-link { 21 | margin: 0; 22 | padding-top: $spacer; 23 | padding-bottom: $spacer; 24 | 25 | color: var(--secondary-color); 26 | 27 | @include hover-focus { 28 | color: var(--secondary-hover-color); 29 | } 30 | 31 | &.active { 32 | color: var(--primary-color); 33 | border-bottom: $border-width solid var(--primary-color) !important; 34 | 35 | @include hover-focus { 36 | color: var(--primary-hover-color); 37 | border-bottom-color: var(--primary-hover-color) !important; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/api/src/DataFixtures/UserFixtures.php: -------------------------------------------------------------------------------- 1 | passwordEncoder = $passwordEncoder; 20 | } 21 | 22 | public function load(ObjectManager $manager) 23 | { 24 | $user = new User(); 25 | $user->setFirstName('Mathieu'); 26 | $user->setLastName('Ledru'); 27 | $user->setPassword($this->passwordEncoder->encodePassword($user, 'admin')); 28 | $user->setEmail('matyo@serverless-starter.com'); 29 | $user->setRoles(['ROLE_SUPER_ADMIN']); 30 | 31 | $manager->persist($user); 32 | $manager->flush(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/api/src/Form/RegisterType.php: -------------------------------------------------------------------------------- 1 | add('email', TextType::class); 26 | $builder->add('password', TextType::class); 27 | } 28 | 29 | public function configureOptions(OptionsResolver $resolver) 30 | { 31 | $resolver->setDefaults([ 32 | 'data_class' => User::class, 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/front/src/views/blog/tags.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {pathTo} from '../../routes' 3 | import {Link} from 'gatsby' 4 | import {faTag} from '@fortawesome/free-solid-svg-icons' 5 | import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' 6 | 7 | const Tags = ({tags}) => { 8 | const orderedTags = Object.keys(tags).sort((tag1, tag2) => { 9 | return tags[tag1] > tags[tag2] 10 | }) 11 | 12 | return ( 13 |
14 |

All tags

15 |
16 |
17 |

{orderedTags.length} tags

18 |

19 | {orderedTags.map(tag => ( 20 | 25 | {tag} ({tags[tag]}) 26 | 27 | ))} 28 |

29 |
30 |
31 |
32 | ) 33 | } 34 | 35 | export default Tags 36 | -------------------------------------------------------------------------------- /packages/front/src/views/blog/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {StaticQuery, graphql} from 'gatsby' 3 | import ArticleItem from './articleItem' 4 | 5 | const Blog = () => ( 6 | <> 7 |
8 |

Blog

9 | 25 | data.allMdx.edges.map((item, i) => ( 26 |
27 |
28 | 29 |
30 |
31 | )) 32 | } 33 | /> 34 |
35 | 36 | ) 37 | 38 | export default Blog 39 | -------------------------------------------------------------------------------- /packages/api/src/Form/SettingsType.php: -------------------------------------------------------------------------------- 1 | add('firstname', TextType::class); 22 | $builder->add('lastname', TextType::class); 23 | $builder->add('username', TextType::class); 24 | $builder->add('apiKey', TextType::class); 25 | $builder->add('facebookId', TextType::class); 26 | $builder->add('githubId', TextType::class); 27 | } 28 | 29 | public function configureOptions(OptionsResolver $resolver) 30 | { 31 | $resolver->setDefaults([ 32 | 'data_class' => User::class, 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mathieu Ledru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/api/config/packages/nyholm_psr7.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | # Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories) 3 | Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory' 4 | Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory' 5 | Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory' 6 | Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory' 7 | Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory' 8 | Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory' 9 | 10 | # Register nyholm/psr7 services for autowiring with HTTPlug factories 11 | Http\Message\MessageFactory: '@nyholm.psr7.httplug_factory' 12 | Http\Message\RequestFactory: '@nyholm.psr7.httplug_factory' 13 | Http\Message\ResponseFactory: '@nyholm.psr7.httplug_factory' 14 | Http\Message\StreamFactory: '@nyholm.psr7.httplug_factory' 15 | Http\Message\UriFactory: '@nyholm.psr7.httplug_factory' 16 | 17 | nyholm.psr7.psr17_factory: 18 | class: Nyholm\Psr7\Factory\Psr17Factory 19 | 20 | nyholm.psr7.httplug_factory: 21 | class: Nyholm\Psr7\Factory\HttplugFactory 22 | -------------------------------------------------------------------------------- /packages/api/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mathieu Ledru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/front/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mathieu Ledru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/front/src/reducers/app/actions.js: -------------------------------------------------------------------------------- 1 | import {COMMIT_SET_PAGE, COMMIT_SET_THEME} from './actions-types' 2 | 3 | export const commitSetPage = page => { 4 | return dispatch => { 5 | dispatch({ 6 | type: COMMIT_SET_PAGE, 7 | page, 8 | }) 9 | return Promise.resolve() 10 | } 11 | } 12 | 13 | export const commitSetTheme = theme => { 14 | return dispatch => { 15 | dispatch({ 16 | type: COMMIT_SET_THEME, 17 | theme, 18 | }) 19 | return Promise.resolve() 20 | } 21 | } 22 | 23 | export const switchTheme = oldTheme => { 24 | return dispatch => { 25 | let theme = 'light' 26 | if (oldTheme === 'light') { 27 | theme = 'dark' 28 | } else if (oldTheme === 'dark') { 29 | theme = 'sepia' 30 | } else if (oldTheme === 'sepia') { 31 | theme = 'light' 32 | } 33 | 34 | localStorage.setItem('theme', JSON.stringify(theme)) 35 | 36 | return dispatch(commitSetTheme(theme)) 37 | } 38 | } 39 | 40 | export const applyTheme = () => { 41 | return dispatch => { 42 | const theme = JSON.parse(localStorage.getItem('theme')) || 'light' 43 | 44 | return dispatch(commitSetTheme(theme)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/api/src/Entity/Traits/TimestampTrait.php: -------------------------------------------------------------------------------- 1 | created; 30 | } 31 | 32 | public function setCreated(DateTime $created): self 33 | { 34 | $this->created = $created; 35 | 36 | return $this; 37 | } 38 | 39 | public function getUpdated(): DateTime 40 | { 41 | return $this->updated; 42 | } 43 | 44 | public function setUpdated(DateTime $updated): self 45 | { 46 | $this->updated = $updated; 47 | 48 | return $this; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/api/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | src 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/api/src/Services/ContactService.php: -------------------------------------------------------------------------------- 1 | em = $em; 27 | $this->contactRepository = $this->em->getRepository(Contact::class); 28 | } 29 | 30 | public function save(Contact $contact): Contact 31 | { 32 | $contact->setUpdated(new \DateTime()); 33 | 34 | $this->em->persist($contact); 35 | $this->em->flush(); 36 | 37 | return $contact; 38 | } 39 | 40 | public function remove(Contact $contact): void 41 | { 42 | $this->em->remove($contact); 43 | $this->em->flush(); 44 | } 45 | 46 | public function findOne(?int $id = null): ?Contact 47 | { 48 | return $this->contactRepository->findOne($id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/front/src/helpers/require-authentication.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {pathTo} from '../routes' 4 | import {isGranted} from '../reducers/user/actions' 5 | import {navigate} from 'gatsby' 6 | 7 | export default function requireAuthentication(Component, role = 'ROLE_USER') { 8 | class requireAuthenticationHelper extends React.Component { 9 | componentDidMount() { 10 | this.checkAuth(this.props.isAuthenticated, this.props.user) 11 | } 12 | 13 | componentDidUpdate(prevProps) { 14 | this.checkAuth(this.props.isAuthenticated, this.props.user) 15 | } 16 | 17 | checkAuth = (isAuthenticated, user) => { 18 | if (!isAuthenticated || (user.username && !isGranted(user, role))) { 19 | if (typeof window !== `undefined`) { 20 | navigate(pathTo('login')) 21 | } 22 | } 23 | } 24 | 25 | render() { 26 | if (this.props.isAuthenticated === true) { 27 | return 28 | } 29 | 30 | return null 31 | } 32 | } 33 | 34 | return connect(state => ({ 35 | token: state.auth.token, 36 | user: state.user, 37 | isAuthenticated: state.auth.isAuthenticated, 38 | }))(requireAuthenticationHelper) 39 | } 40 | -------------------------------------------------------------------------------- /packages/front/src/views/blog/tag.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {pathTo} from '../../routes' 3 | import {Link} from 'gatsby' 4 | import ArticleItem from './articleItem' 5 | 6 | class Tag extends Component { 7 | render() { 8 | const {tag, articles} = this.props 9 | 10 | return ( 11 |
12 |

Tag

13 |
14 |
15 |

16 | {articles.edges.length} posts tagged with {tag} 17 |

18 |

19 | 20 | View all tags 21 | 22 |

23 |
24 |
25 |
26 |
27 | {articles.edges.map((item, i) => ( 28 |
29 |
30 | 31 |
32 |
33 | ))} 34 |
35 |
36 |
37 | ) 38 | } 39 | } 40 | 41 | export default Tag 42 | -------------------------------------------------------------------------------- /packages/api/src/Services/ConfigService.php: -------------------------------------------------------------------------------- 1 | em = $em; 27 | $this->configRepository = $this->em->getRepository(Config::class); 28 | } 29 | 30 | public function save(Config $config): Config 31 | { 32 | $config->setUpdated(new \DateTime()); 33 | 34 | $this->em->persist($config); 35 | $this->em->flush(); 36 | 37 | return $config; 38 | } 39 | 40 | public function remove(Config $config): void 41 | { 42 | $this->em->remove($config); 43 | $this->em->flush(); 44 | } 45 | 46 | public function findOne(?int $id = null): ?Config 47 | { 48 | return $this->configRepository->findOne($id); 49 | } 50 | 51 | public function getJson(Config $config): array 52 | { 53 | return []; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/api/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 6 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 7 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 8 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 9 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 11 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 12 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 13 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 14 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 15 | Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], 16 | ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], 17 | Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], 18 | Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], 19 | ]; 20 | -------------------------------------------------------------------------------- /packages/front/src/templates/contributor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Contributor} from '../views' 3 | import {graphql} from 'gatsby' 4 | import {withPage} from '../helpers' 5 | 6 | export default ({data, location}) => { 7 | const {contributor} = data 8 | 9 | const ContributorPage = withPage(Contributor, 'contributor', { 10 | location: location, 11 | title: contributor.name, 12 | description: contributor.description, 13 | image: contributor.image.publicURL, 14 | }) 15 | 16 | return 17 | } 18 | 19 | export const query = graphql` 20 | query($slug: String) { 21 | contributor: contributorsYaml(fields: { slug: { eq: $slug } }) { 22 | name 23 | description 24 | twitter 25 | image { 26 | childImageSharp { 27 | fluid { 28 | ...GatsbyImageSharpFluid 29 | } 30 | } 31 | publicURL 32 | } 33 | } 34 | articles: allMdx( 35 | filter: { 36 | fields: { sourceName: { eq: "blog" } } 37 | frontmatter: { author: { fields: { slug: { eq: $slug } } } } 38 | } 39 | sort: { fields: frontmatter___date, order: DESC } 40 | ) { 41 | edges { 42 | node { 43 | ...ArticleItemFragment 44 | } 45 | } 46 | } 47 | } 48 | ` 49 | -------------------------------------------------------------------------------- /packages/front/src/views/admin.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {fetchConfig, updateConfig} from '../reducers/config/actions' 3 | import {connect} from 'react-redux' 4 | 5 | class Admin extends Component { 6 | state = { 7 | config: {}, 8 | isSaving: false, 9 | } 10 | 11 | componentDidMount() { 12 | this.props.dispatch(fetchConfig(this.props.auth.token)).then(response => { 13 | this.setState({ 14 | config: Object.assign({}, this.state.config, response.data), 15 | }) 16 | }) 17 | } 18 | 19 | onUpdate = event => { 20 | if (event) { 21 | event.preventDefault() 22 | } 23 | 24 | this.setState({isSaving: true}, () => { 25 | this.props 26 | .dispatch(updateConfig(this.state.config, this.props.auth.token)) 27 | .then(() => { 28 | this.setState({isSaving: false}) 29 | }) 30 | }) 31 | } 32 | 33 | render() { 34 | return ( 35 | <> 36 |
37 |

Admin

38 |
39 |
40 |
41 | 42 | ) 43 | } 44 | } 45 | 46 | export default connect(state => { 47 | return { 48 | auth: state.auth, 49 | env: state.env, 50 | user: state.user, 51 | } 52 | })(Admin) 53 | -------------------------------------------------------------------------------- /packages/front/src/reducers/config/actions.js: -------------------------------------------------------------------------------- 1 | import request from 'axios' 2 | import server from '../../utils/server' 3 | import {commitLogoutUser} from '../auth/actions' 4 | import {commitAddLog} from '../logs/actions' 5 | 6 | export const fetchConfig = token => { 7 | return dispatch => { 8 | return request 9 | .get(`${server.getBaseUrl()}/api/config/get-config`, { 10 | headers: { 11 | 'ServerlessStarter-Authorization': `Bearer ${token}`, 12 | }, 13 | }) 14 | .catch(error => { 15 | if (error.request.status === 401) { 16 | dispatch(commitLogoutUser()) 17 | } else { 18 | throw error 19 | } 20 | }) 21 | } 22 | } 23 | export const updateConfig = (item, token) => { 24 | return dispatch => { 25 | let data = {} 26 | 27 | return request 28 | .put(`${server.getBaseUrl()}/api/config/set-config`, data, { 29 | headers: { 30 | 'ServerlessStarter-Authorization': `Bearer ${token}`, 31 | }, 32 | }) 33 | .then(response => { 34 | return data 35 | }) 36 | .catch(error => { 37 | if (error.request.status === 400) { 38 | dispatch(commitAddLog(error.response.data.message)) 39 | } else if (error.request.status === 401) { 40 | dispatch(commitLogoutUser()) 41 | } else { 42 | throw error 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/front/src/reducers/auth/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | COMMIT_LOGIN_USER_REQUEST, 3 | COMMIT_LOGIN_USER_SUCCESS, 4 | COMMIT_LOGIN_USER_FAILURE, 5 | COMMIT_LOGOUT_USER, 6 | } from './actions-types' 7 | 8 | const defaultState = { 9 | token: null, 10 | isAuthenticated: false, 11 | isAuthenticating: false, 12 | statusText: null, 13 | } 14 | 15 | const auth = (state = defaultState, action) => { 16 | switch (action.type) { 17 | case COMMIT_LOGIN_USER_REQUEST: 18 | return Object.assign({}, state, { 19 | isAuthenticating: true, 20 | statusText: null, 21 | }) 22 | case COMMIT_LOGIN_USER_SUCCESS: 23 | return Object.assign({}, state, { 24 | isAuthenticating: false, 25 | isAuthenticated: true, 26 | token: action.token, 27 | statusText: 'You have been successfully logged in.', 28 | }) 29 | case COMMIT_LOGIN_USER_FAILURE: 30 | return Object.assign({}, state, { 31 | isAuthenticating: false, 32 | isAuthenticated: false, 33 | token: null, 34 | statusText: `Authentication Error: ${action.status} ${action.statusText}`, 35 | }) 36 | case COMMIT_LOGOUT_USER: 37 | return Object.assign({}, state, { 38 | isAuthenticated: false, 39 | token: null, 40 | statusText: 'You have been successfully logged out.', 41 | }) 42 | default: 43 | return state 44 | } 45 | } 46 | 47 | export default auth 48 | -------------------------------------------------------------------------------- /packages/api/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 24 | putenv('APP_ENV=' . $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 25 | } 26 | 27 | if ($input->hasParameterOption('--no-debug', true)) { 28 | putenv('APP_DEBUG=' . $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 29 | } 30 | 31 | (new Dotenv())->bootEnv(dirname(__DIR__) . '/.env'); 32 | 33 | if ($_SERVER['APP_DEBUG']) { 34 | umask(0000); 35 | 36 | if (class_exists(Debug::class)) { 37 | Debug::enable(); 38 | } 39 | } 40 | 41 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']); 42 | $application = new Application($kernel); 43 | $application->run($input); 44 | -------------------------------------------------------------------------------- /packages/front/bin/serverless-starter-front: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | 5 | // Make sure that it also find the config folder when it 6 | // did get started from another folder that the root one. 7 | process.env.NODE_CONFIG_DIR = process.env.NODE_CONFIG_DIR || path.join(__dirname, 'config'); 8 | 9 | // Check if version should be displayed 10 | var versionFlags = [ 11 | '-v', 12 | '-V', 13 | '--version' 14 | ]; 15 | if (versionFlags.includes(process.argv.slice(-1)[0])) { 16 | console.log('ServerlessStarter client v' + require('../package').version); 17 | process.exit(0); 18 | } 19 | 20 | if (process.argv.length === 2) { 21 | // When no command is given choose by default start 22 | process.argv.push('start'); 23 | } 24 | 25 | var command = process.argv[2]; 26 | 27 | // Check if the command the user did enter is supported else stop 28 | var supportedCommands = [ 29 | 'help', 30 | 'start', 31 | ]; 32 | 33 | if (!supportedCommands.includes(command)) { 34 | console.log('\nThe command "' + command + '" is not known!\n'); 35 | process.argv.pop(); 36 | process.argv.push('--help'); 37 | } 38 | 39 | if (parseInt(process.versions.node.split('.')[0], 10) < 10) { 40 | console.log('\nThe Node.js version is too old to run serverless-starter. Please use version 10 or later!\n'); 41 | process.exit(0); 42 | } 43 | 44 | require('@oclif/command').run() 45 | .then(require('@oclif/command/flush')) 46 | .catch(require('@oclif/errors/handle')); 47 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_fonts.scss: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | } 5 | 6 | @font-face { 7 | font-family: Jost; 8 | src: local('Jost'), url(../fonts/Jost-400-Book.otf); 9 | font-weight: 400; 10 | font-display: swap; 11 | } 12 | 13 | @font-face { 14 | font-family: Jost; 15 | src: local('Jost'), url(../fonts/Jost-400-BookItalic.otf); 16 | font-weight: 400; 17 | font-display: swap; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: Jost; 23 | src: local('Jost'), url(../fonts/Jost-500-Medium.otf); 24 | font-weight: 500; 25 | font-display: swap; 26 | } 27 | 28 | @font-face { 29 | font-family: Jost; 30 | src: local('Jost'), url(../fonts/Jost-500-MediumItalic.otf); 31 | font-weight: 500; 32 | font-display: swap; 33 | font-style: italic; 34 | } 35 | 36 | @font-face { 37 | font-family: Jost; 38 | src: local('Jost'), url(../fonts/Jost-600-Semi.otf); 39 | font-weight: 600; 40 | font-display: swap; 41 | } 42 | 43 | @font-face { 44 | font-family: Jost; 45 | src: local('Jost'), url(../fonts/Jost-600-SemiItalic.otf); 46 | font-style: italic; 47 | font-weight: 600; 48 | font-display: swap; 49 | } 50 | 51 | @font-face { 52 | font-family: Jost; 53 | src: local('Jost'), url(../fonts/Jost-700-Bold.otf); 54 | font-weight: 700; 55 | font-display: swap; 56 | } 57 | 58 | .svg-inline--fa { 59 | display: inline-block; 60 | font-size: inherit; 61 | height: 1em; 62 | overflow: visible; 63 | vertical-align: -0.125em; 64 | } 65 | -------------------------------------------------------------------------------- /packages/front/src/templates/article.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Article} from '../views' 3 | import {graphql} from 'gatsby' 4 | import {withPage} from '../helpers' 5 | 6 | export default ({data, location, pageContext: {previous, next}}) => { 7 | const {article} = data 8 | 9 | const ArticlePage = withPage(Article, 'article', { 10 | location: location, 11 | title: article.frontmatter.title, 12 | description: article.excerpt, 13 | image: article.frontmatter.coverSeo.publicURL, 14 | type: 'article', 15 | }) 16 | 17 | return 18 | } 19 | 20 | export const query = graphql` 21 | query($id: String) { 22 | article: mdx(id: { eq: $id }) { 23 | body 24 | timeToRead 25 | excerpt 26 | frontmatter { 27 | title 28 | author { 29 | fields { 30 | slug 31 | } 32 | name 33 | image { 34 | childImageSharp { 35 | fixed(width: 36, height: 36) { 36 | ...GatsbyImageSharpFixed 37 | } 38 | } 39 | } 40 | } 41 | cover { 42 | childImageSharp { 43 | fluid { 44 | ...GatsbyImageSharpFluid 45 | } 46 | } 47 | extension 48 | publicURL 49 | } 50 | coverSeo { 51 | publicURL 52 | } 53 | coverAuthor 54 | coverOriginalUrl 55 | tags 56 | date(formatString: "MMMM Do YYYY") 57 | } 58 | } 59 | } 60 | ` 61 | -------------------------------------------------------------------------------- /packages/api/config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 3 | encoders: 4 | App\Entity\User: 5 | algorithm: bcrypt 6 | 7 | role_hierarchy: 8 | ROLE_SUPER_ADMIN: ROLE_USER 9 | providers: 10 | app_api_key_user_provider: 11 | id: App\Security\ApiKeyUserProvider 12 | firewalls: 13 | dev: 14 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 15 | security: false 16 | login: 17 | pattern: ^/api/login_check 18 | stateless: true 19 | anonymous: true 20 | json_login: 21 | provider: app_api_key_user_provider 22 | check_path: /api/login_check 23 | success_handler: lexik_jwt_authentication.handler.authentication_success 24 | failure_handler: lexik_jwt_authentication.handler.authentication_failure 25 | api: 26 | pattern: ^/api 27 | stateless: true 28 | anonymous: true 29 | guard: 30 | provider: app_api_key_user_provider 31 | authenticators: 32 | - lexik_jwt_authentication.jwt_token_authenticator 33 | - App\Security\ApiKeyAuthenticator 34 | entry_point: lexik_jwt_authentication.jwt_token_authenticator 35 | 36 | # Easy way to control access for large sections of your site 37 | # Note: Only the *first* access control that matches will be used 38 | access_control: 39 | #- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 40 | #- { path: ^/api/register, roles: IS_AUTHENTICATED_ANONYMOUSLY } 41 | #- { path: ^/api, roles: ROLE_USER } 42 | -------------------------------------------------------------------------------- /packages/front/src/components/mdx-provider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {MDXProvider} from '@mdx-js/react' 3 | import {Link} from 'gatsby' 4 | import {Ace} from "../components"; 5 | 6 | const components = { 7 | h1: props => ( 8 |

9 | {props.children} 10 |

11 | ), 12 | h2: props =>

{props.children}

, 13 | h3: props =>
{props.children}
, 14 | h4: props =>
{props.children}
, 15 | a: props => { 16 | if (props.href.indexOf('https://serverless-starter.com') === 0) { 17 | const to = props.href.slice(30) 18 | return {props.children} 19 | } 20 | 21 | return 22 | {props.children} 23 | 24 | }, 25 | img: props => 26 | {props.alt} 27 | , 28 | Link, 29 | code: props => { 30 | let mode = "" 31 | if (props.className === "language-bash") { 32 | mode = "batchfile" 33 | } 34 | if (props.className === "language-javascript") { 35 | mode = "javascript" 36 | } 37 | if (props.className === "language-jsx") { 38 | mode = "jsx" 39 | } 40 | 41 | return 42 | 47 | 48 | } 49 | } 50 | 51 | export default props => ( 52 | {props.children} 53 | ) 54 | -------------------------------------------------------------------------------- /packages/front/src/components/checkbox.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | let id = 0 4 | const gen = () => { 5 | id += 2 6 | 7 | return `checkbox_${id}` 8 | } 9 | 10 | export default class Checkbox extends Component { 11 | onChange = event => { 12 | const {value} = this.props 13 | 14 | if (this.props.onChange) { 15 | this.props.onChange(!value) 16 | } 17 | } 18 | 19 | render() { 20 | const {value, label, className} = this.props 21 | const uid = gen() 22 | return ( 23 | <> 24 |
29 | 36 | 39 |
40 |
45 | 52 | 55 |
56 | 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/front/src/views/login/github.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {navigate} from 'gatsby' 3 | import {pathTo} from '../../routes' 4 | import {githubLogin} from '../../reducers/auth/actions' 5 | import {commitAddLog} from '../../reducers/logs/actions' 6 | import {connect} from 'react-redux' 7 | 8 | class GithubLogin extends Component { 9 | componentDidMount() { 10 | let code = this.getCode() 11 | if (code === null) { 12 | if (typeof window !== `undefined`) { 13 | return navigate(pathTo('login')) 14 | } 15 | } 16 | 17 | this.props.dispatch(githubLogin(code, this.props.auth.token)).then(() => { 18 | if (this.props.auth.isAuthenticated) { 19 | if (typeof window !== `undefined`) { 20 | return navigate(pathTo('settings')) 21 | } 22 | } else { 23 | this.props.dispatch(commitAddLog(this.props.auth.statusText)) 24 | if (typeof window !== `undefined`) { 25 | return navigate(pathTo('login')) 26 | } 27 | } 28 | }) 29 | } 30 | 31 | getCode() { 32 | let m = this.props.location.search.match(/code=([^&]*)/) 33 | if (m) { 34 | return m[1] 35 | } 36 | 37 | return null 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 |

Login Github

44 |

45 | Application is currently logging you from Github 46 |

47 |
48 | ) 49 | } 50 | } 51 | 52 | export default connect(state => { 53 | return { 54 | auth: state.auth, 55 | } 56 | })(GithubLogin) 57 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_buttons.scss: -------------------------------------------------------------------------------- 1 | @each $color, $value in $theme-colors { 2 | .btn-#{$color} { 3 | background-color: var(--#{$color}-color); 4 | border-color: var(--#{$color}-color); 5 | 6 | @include hover { 7 | background-color: var(--#{$color}-hover-color); 8 | border-color: var(--#{$color}-hover-color); 9 | } 10 | } 11 | } 12 | 13 | .btn-social { 14 | color: var(--text-color); 15 | } 16 | 17 | .btn-social { 18 | position: relative; 19 | padding-left: 44px; 20 | text-align: left; 21 | white-space: nowrap; 22 | overflow: hidden; 23 | text-overflow: ellipsis; 24 | 25 | & > :first-child { 26 | position: absolute; 27 | left: 8px; 28 | width: 32px; 29 | padding-right: 8px; 30 | line-height: 34px; 31 | font-size: 1.6em; 32 | text-align: center; 33 | border-right: 1px solid rgba(0, 0, 0, .2); 34 | } 35 | } 36 | 37 | .btn-facebook { 38 | color: #fff; 39 | background-color: #3b5998; 40 | border-color: rgba(0, 0, 0, .2); 41 | 42 | &:hover { 43 | color: #fff; 44 | background-color: #2d4373; 45 | border-color: rgba(0, 0, 0, .2); 46 | } 47 | } 48 | 49 | .btn-twitter { 50 | color: #fff; 51 | background-color: rgb(29, 161, 242); 52 | border-color: rgba(0, 0, 0, .1); 53 | 54 | &:hover { 55 | color: #fff; 56 | background-color: rgb(26, 145, 218); 57 | border-color: rgba(0, 0, 0, .1); 58 | } 59 | } 60 | 61 | .btn-github { 62 | color: #fff; 63 | background-color: #444; 64 | border-color: rgba(0, 0, 0, .2); 65 | 66 | &:hover { 67 | color: #fff; 68 | background-color: #2b2b2b; 69 | border-color: rgba(0, 0, 0, .2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/api/src/Entity/Contact.php: -------------------------------------------------------------------------------- 1 | id; 50 | } 51 | 52 | public function getEmail(): string 53 | { 54 | return $this->email; 55 | } 56 | 57 | public function setEmail(string $email): self 58 | { 59 | $this->email = $email; 60 | 61 | return $this; 62 | } 63 | 64 | public function getMessage(): string 65 | { 66 | return $this->message; 67 | } 68 | 69 | public function setMessage(string $message): self 70 | { 71 | $this->message = $message; 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/front/src/views/login/facebook.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {navigate} from 'gatsby' 3 | import {pathTo} from '../../routes' 4 | import {facebookLogin} from '../../reducers/auth/actions' 5 | import {commitAddLog} from '../../reducers/logs/actions' 6 | import {connect} from 'react-redux' 7 | 8 | class FacebookLogin extends Component { 9 | componentDidMount() { 10 | let accessToken = this.getAccessToken() 11 | if (accessToken === null) { 12 | if (typeof window !== `undefined`) { 13 | return navigate(pathTo('login')) 14 | } 15 | } 16 | 17 | this.props 18 | .dispatch(facebookLogin(accessToken, this.props.auth.token)) 19 | .then(() => { 20 | if (this.props.auth.isAuthenticated) { 21 | if (typeof window !== `undefined`) { 22 | return navigate(pathTo('settings')) 23 | } 24 | } else { 25 | this.props.dispatch(commitAddLog(this.props.auth.statusText)) 26 | if (typeof window !== `undefined`) { 27 | return navigate(pathTo('login')) 28 | } 29 | } 30 | }) 31 | } 32 | 33 | getAccessToken() { 34 | let m = this.props.location.hash.match(/access_token=([^&]*)/) 35 | if (m) { 36 | return m[1] 37 | } 38 | 39 | return null 40 | } 41 | 42 | render() { 43 | return ( 44 |
45 |

Login Facebook

46 |

47 | Application is currently logging you from Facebook 48 |

49 |
50 | ) 51 | } 52 | } 53 | 54 | export default connect(state => { 55 | return { 56 | auth: state.auth, 57 | } 58 | })(FacebookLogin) 59 | -------------------------------------------------------------------------------- /packages/api/src/Security/ApiKeyUserProvider.php: -------------------------------------------------------------------------------- 1 | userService = $userService; 23 | } 24 | 25 | public function loadUserByApiKey($apiKey) 26 | { 27 | // Look up the username based on the token in the database, via 28 | // an API call, or do something entirely different 29 | return $this->userService->findOneByApiKey($apiKey); 30 | } 31 | 32 | /** 33 | * @param string $username 34 | * @return mixed|UserInterface 35 | * @throws \Doctrine\ORM\NonUniqueResultException 36 | */ 37 | public function loadUserByUsername($username) 38 | { 39 | return $this->userService->findOneByEmailOrUsername($username); 40 | } 41 | 42 | public function refreshUser(UserInterface $user) 43 | { 44 | // this is used for storing authentication in the session 45 | // but in this example, the token is sent in each request, 46 | // so authentication can be stateless. Throwing this exception 47 | // is proper to make things stateless 48 | throw new UnsupportedUserException(); 49 | } 50 | 51 | public function supportsClass($class) 52 | { 53 | return User::class === $class; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/api/src/Doctrine/CurrentUserExtension.php: -------------------------------------------------------------------------------- 1 | security = $security; 20 | } 21 | 22 | public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) 23 | { 24 | $this->addWhere($queryBuilder, $resourceClass); 25 | } 26 | 27 | public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []) 28 | { 29 | $this->addWhere($queryBuilder, $resourceClass); 30 | } 31 | 32 | private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void 33 | { 34 | if (Program::class === $resourceClass) { 35 | $rootAlias = $queryBuilder->getRootAliases()[0]; 36 | $queryBuilder->andWhere($queryBuilder->expr()->orX( 37 | sprintf('%s.user = :current_user', $rootAlias), 38 | sprintf('%s.public = true', $rootAlias) 39 | )); 40 | $queryBuilder->setParameter('current_user', $this->security->getUser()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/front/src/assets/styles/_user.scss: -------------------------------------------------------------------------------- 1 | .user { 2 | overflow: hidden; 3 | background-color: var(--bg-color); 4 | background-clip: border-box; 5 | border: $card-border-width solid var(--border-color); 6 | @include border-radius($card-border-radius); 7 | } 8 | 9 | .user-header { 10 | background-color: $light; 11 | padding: 0 !important; 12 | background-position: center; 13 | background-repeat: no-repeat; 14 | background-size: cover; 15 | height: 150px; 16 | position: relative; 17 | display: flex; 18 | justify-content: center; 19 | text-align: center; 20 | 21 | .profile_pic { 22 | position: absolute; 23 | bottom: -50px; 24 | height: 112px; 25 | width: 112px; 26 | padding: 5px; 27 | border: 2px solid $primary; 28 | border-radius: 50%; 29 | 30 | img { 31 | height: 100px; 32 | width: 100px; 33 | border-radius: 50%; 34 | } 35 | } 36 | } 37 | 38 | .user-body { 39 | padding-top: 55px !important; 40 | padding-right: $spacer; 41 | padding-left: $spacer; 42 | padding-bottom: $spacer; 43 | border-top: $card-border-width solid $card-border-color; 44 | 45 | .name_container { 46 | display: flex; 47 | justify-content: center; 48 | margin-top: 5px; 49 | margin-bottom: 5px; 50 | } 51 | 52 | .name { 53 | color: gray; 54 | position: relative; 55 | } 56 | 57 | .description_container { 58 | display: flex; 59 | justify-content: center; 60 | } 61 | 62 | .description { 63 | position: relative; 64 | text-align: center; 65 | } 66 | 67 | .follow { 68 | padding-top: 20px; 69 | display: flex; 70 | justify-content: center; 71 | } 72 | 73 | .follow_btn { 74 | background: #2196F3; 75 | padding: 7px; 76 | color: #fff; 77 | border-radius: 12px; 78 | cursor: pointer; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/front/src/components/search.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Select} from '../components' 3 | import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' 4 | import {faDotCircle} from '@fortawesome/free-regular-svg-icons' 5 | 6 | export default class Search extends Component { 7 | state = { 8 | search: '@serverless-starter-com/serverless-starter-flow-javascript', 9 | } 10 | 11 | onSubmit = event => { 12 | event.preventDefault() 13 | 14 | if (this.props.onPush) { 15 | this.props.onPush(this.state.search) 16 | } 17 | } 18 | 19 | onChange = value => { 20 | this.setState({search: value}) 21 | } 22 | 23 | render() { 24 | const {userFlows} = this.props 25 | const {search} = this.state 26 | 27 | return ( 28 |
29 |
30 | 33 |
34 |
35 |
36 | 73 |
74 | 81 | 82 | 108 |
109 | ) 110 | } 111 | } 112 | 113 | export default Navigation 114 | -------------------------------------------------------------------------------- /packages/front/src/helpers/with-page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {commitSetPage} from '../reducers/app/actions' 4 | import Helmet from 'react-helmet' 5 | 6 | export default function withPage(Component, page, seo) { 7 | class PageHelper extends React.Component { 8 | componentDidMount() { 9 | this.props.dispatch(commitSetPage(page)) 10 | } 11 | 12 | render() { 13 | const {env} = this.props 14 | 15 | return ( 16 | <> 17 | {seo && ( 18 | 19 | {seo.title} 20 | 24 | {seo.description && ( 25 | 26 | )} 27 | 28 | 29 | {seo.description && ( 30 | 31 | )} 32 | 36 | {seo.type && } 37 | {seo.title && } 38 | {seo.image && 39 | typeof seo.image === 'string' && [ 40 | , 45 | , 50 | , 55 | ]} 56 | {seo.image && 57 | typeof seo.image !== 'string' && [ 58 | , 63 | , 68 | , 73 | ]} 74 | 75 | {seo.title && } 76 | {seo.description && ( 77 | 78 | )} 79 | {seo.image && typeof seo.image === 'string' && ( 80 | 81 | )} 82 | {seo.image && typeof seo.image !== 'string' && ( 83 | 87 | )} 88 | 89 | )} 90 | 91 | 92 | ) 93 | } 94 | } 95 | 96 | return connect(state => ({ 97 | env: state.env, 98 | app: state.app, 99 | }))(PageHelper) 100 | } 101 | -------------------------------------------------------------------------------- /packages/front/src/commands/start.ts: -------------------------------------------------------------------------------- 1 | import {Command, flags} from '@oclif/command'; 2 | import * as open from 'open'; 3 | 4 | import config from '../config'; 5 | import app from "../server"; 6 | 7 | let processExistCode = 0; 8 | 9 | export class Start extends Command { 10 | static description = 'Starts serverless-starter client.'; 11 | 12 | static examples = [ 13 | `$ serverless-starter-front start`, 14 | `$ serverless-starter-front start -o`, 15 | ]; 16 | 17 | static flags = { 18 | help: flags.help({char: 'h'}), 19 | open: flags.boolean({ 20 | char: 'o', 21 | description: 'opens the UI automatically in browser', 22 | }), 23 | }; 24 | 25 | /** 26 | * Opens the UI in browser 27 | */ 28 | static openBrowser() { 29 | const url = `http://localhost:${config.get('port')}`; 30 | 31 | open(url, {wait: true}) 32 | .catch((error: Error) => { 33 | console.log(`\nWas not able to open URL in browser. Please open manually by visiting:\n${url}\n`); 34 | }); 35 | } 36 | 37 | /** 38 | * Stoppes serverless-starter in a graceful way. 39 | */ 40 | static async stopProcess() { 41 | console.log(`\nStopping serverless-starter...`); 42 | 43 | setTimeout(() => { 44 | // In case that something goes wrong with shutdown we 45 | // kill after max. 30 seconds no matter what 46 | process.exit(processExistCode); 47 | }, 30000); 48 | 49 | process.exit(processExistCode); 50 | } 51 | 52 | async run() { 53 | // Make sure that serverless-starter shuts down gracefully if possible 54 | process.on('SIGTERM', Start.stopProcess); 55 | process.on('SIGINT', Start.stopProcess); 56 | 57 | const {flags} = this.parse(Start); 58 | 59 | // Wrap that the process does not close but we can still use async 60 | (async () => { 61 | try { 62 | await app(); 63 | 64 | const url = `http://localhost:${config.get('port')}`; 65 | this.log(`\nServerlessStarter client v${require('../../package.json').version} is ready on:\n${url}`); 66 | 67 | // Allow to open serverless-starter editor by pressing "o" 68 | if (Boolean(process.stdout.isTTY) && process.stdin.setRawMode) { 69 | process.stdin.setRawMode(true); 70 | process.stdin.resume(); 71 | process.stdin.setEncoding('utf8'); 72 | let inputText = ''; 73 | 74 | if (flags.open) { 75 | Start.openBrowser(); 76 | } 77 | this.log(`\nPress "o" to open in Browser.`); 78 | process.stdin.on("data", (key: string) => { 79 | if (key === 'o') { 80 | Start.openBrowser(); 81 | inputText = ''; 82 | } else if (key.charCodeAt(0) === 3) { 83 | // Ctrl + c got pressed 84 | Start.stopProcess(); 85 | } else { 86 | // When anything else got pressed, record it and send it on enter into the child process 87 | if (key.charCodeAt(0) === 13) { 88 | // send to child process and print in terminal 89 | process.stdout.write('\n'); 90 | inputText = ''; 91 | } else { 92 | // record it and write into terminal 93 | inputText += key; 94 | process.stdout.write(key); 95 | } 96 | } 97 | }); 98 | } 99 | } catch (error) { 100 | this.error(`There was an error: ${error.message}`); 101 | 102 | processExistCode = 1; 103 | // @ts-ignore 104 | process.emit('SIGINT'); 105 | } 106 | })(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/front/src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/front/src/views/blog/article.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Link} from 'gatsby' 3 | import {MDXRenderer} from 'gatsby-plugin-mdx' 4 | import Img from 'gatsby-image' 5 | import {faArrowLeft, faArrowRight} from '@fortawesome/free-solid-svg-icons' 6 | import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' 7 | import {pathTo} from '../../routes' 8 | import {MDXProvider} from '../../components' 9 | 10 | class Article extends Component { 11 | render() { 12 | const {article, previous, next} = this.props 13 | 14 | return ( 15 |
16 |
17 | {article.frontmatter.cover.childImageSharp && ( 18 | Cover 23 | )} 24 | {article.frontmatter.cover.extension === 'svg' && ( 25 | Cover 30 | )} 31 |
32 | credit {article.frontmatter.coverAuthor} 35 |
36 |
37 | 38 |

{article.frontmatter.title}

39 | 40 |
41 |
42 | 43 | Posted {article.frontmatter.date} by 44 | {article.frontmatter.author.name} 52 | 57 | {article.frontmatter.author.name} 58 | 59 | - 60 | {article.timeToRead} min read 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 | {article.body} 69 | 70 |
71 |
72 | 73 |
74 |
75 | {previous && ( 76 | 80 | {' '} 81 | {previous.frontmatter.title} 82 | 83 | )} 84 |
85 |
86 | {next && ( 87 | 91 | {next.frontmatter.title} 92 | 93 | )} 94 |
95 |
96 |
97 | ) 98 | } 99 | } 100 | 101 | export default Article 102 | -------------------------------------------------------------------------------- /packages/front/src/components/select.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {default as ReactSelect} from 'react-select' 3 | import {default as ReactCreatableSelect} from 'react-select/creatable' 4 | import {connect} from 'react-redux' 5 | 6 | class Select extends Component { 7 | onChange = data => { 8 | const {onChange, multiple} = this.props 9 | if (onChange) { 10 | if (multiple === true) { 11 | onChange( 12 | (data || []).map(option => { 13 | return option.value 14 | }) 15 | ) 16 | } else { 17 | onChange(data.value) 18 | } 19 | } 20 | } 21 | 22 | render() { 23 | const {value, options, edit, multiple, app} = this.props 24 | const customStyles = { 25 | light: { 26 | menu: provided => ({ 27 | ...provided, 28 | zIndex: 10, 29 | }), 30 | multiValue: styles => ({ 31 | ...styles, 32 | backgroundColor: '#6c757d', 33 | }), 34 | multiValueLabel: styles => ({ 35 | ...styles, 36 | color: '#FFFFFF', 37 | }), 38 | }, 39 | dark: { 40 | menu: provided => ({ 41 | ...provided, 42 | zIndex: 10, 43 | }), 44 | multiValue: styles => ({ 45 | ...styles, 46 | backgroundColor: '#576068', 47 | }), 48 | multiValueLabel: styles => ({ 49 | ...styles, 50 | color: '#ebf4f1', 51 | }), 52 | }, 53 | sepia: { 54 | menu: provided => ({ 55 | ...provided, 56 | zIndex: 10, 57 | }), 58 | multiValue: styles => ({ 59 | ...styles, 60 | backgroundColor: '#876944', 61 | }), 62 | multiValueLabel: styles => ({ 63 | ...styles, 64 | color: '#eadec2', 65 | }), 66 | }, 67 | } 68 | const themes = { 69 | light: theme => theme, 70 | dark: theme => ({ 71 | ...theme, 72 | colors: { 73 | ...theme.colors, 74 | primary25: '#0056b3', 75 | neutral0: '#0e2233', 76 | neutral5: '#0f2b3d', 77 | neutral10: '#576068', 78 | neutral20: '#002651', 79 | neutral70: '#002651', 80 | neutral80: '#495057', 81 | dangerLight: '#d59b8b', 82 | }, 83 | }), 84 | sepia: theme => ({ 85 | ...theme, 86 | colors: { 87 | ...theme.colors, 88 | primary25: '#0056b3', 89 | neutral0: '#eadec2', 90 | neutral5: '#0f2b3d', 91 | neutral10: '#876944', 92 | neutral20: '#ded0bf', 93 | neutral70: '#002651', 94 | neutral80: '#495057', 95 | dangerLight: '#d59b8b', 96 | }, 97 | }), 98 | } 99 | 100 | let selectOptions = options || [] 101 | let selectValue = undefined 102 | 103 | if (multiple === true) { 104 | selectValue = [] 105 | if (value) { 106 | selectValue = value.map(data => { 107 | return {value: data, label: data} 108 | }) 109 | } 110 | } else { 111 | selectValue = selectOptions.filter(option => { 112 | return option.value === value 113 | }) 114 | } 115 | 116 | const DisplaySelect = edit === true ? ReactCreatableSelect : ReactSelect 117 | 118 | return ( 119 | 127 | ) 128 | } 129 | } 130 | 131 | export default connect(state => ({ 132 | app: state.app, 133 | }))(Select) 134 | -------------------------------------------------------------------------------- /packages/front/src/views/contact.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {connect} from 'react-redux' 3 | import {contact} from '../reducers/contact/actions' 4 | 5 | class Contact extends Component { 6 | state = { 7 | email: null, 8 | emailError: false, 9 | message: null, 10 | messageError: false, 11 | sent: false, 12 | } 13 | 14 | onChangeEmail = event => { 15 | this.setState({email: event.target.value}) 16 | } 17 | 18 | onChangeMessage = event => { 19 | this.setState({message: event.target.value}) 20 | } 21 | 22 | onSubmit = e => { 23 | e.preventDefault() 24 | 25 | let valid = true 26 | if (!this.state.email) { 27 | valid = false 28 | this.setState({emailError: true}) 29 | } else { 30 | this.setState({emailError: false}) 31 | } 32 | 33 | if (!this.state.message) { 34 | valid = false 35 | this.setState({messageError: true}) 36 | } else { 37 | this.setState({messageError: false}) 38 | } 39 | 40 | if (valid) { 41 | this.props 42 | .dispatch(contact(this.state.email, this.state.message)) 43 | .then(data => { 44 | if (data === true) { 45 | this.setState({sent: true}) 46 | } 47 | }) 48 | } 49 | } 50 | 51 | render() { 52 | const {email, emailError, message, messageError, sent} = this.state 53 | 54 | return ( 55 |
56 |
57 |
58 |

Contact

59 | {sent && ( 60 |

Your message has been sent

61 | )} 62 | {!sent && [ 63 |

64 | You got a question about ServerlessStarter, write more here 65 |
66 | It will be a pleasure to respond 67 |

, 68 |
69 |
70 | 80 | {emailError && ( 81 |
85 | Enter your email 86 |
87 | )} 88 |
89 | 90 |
91 |