├── .circleci └── config.yml ├── .github └── workflows │ └── manual.yml ├── CODEOWNERS ├── LICENSE.txt ├── README.md ├── package.json └── udagram ├── .gitignore ├── set_env.sh ├── udagram-api ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── Procfile ├── mock │ ├── xander0.jpg │ ├── xander1.jpg │ └── xander2.jpg ├── package-lock.json ├── package.json ├── src │ ├── aws.ts │ ├── config │ │ ├── README.md │ │ └── config.ts │ ├── controllers │ │ └── v0 │ │ │ ├── feed │ │ │ ├── models │ │ │ │ └── FeedItem.ts │ │ │ └── routes │ │ │ │ └── feed.router.ts │ │ │ ├── index.router.ts │ │ │ ├── model.index.ts │ │ │ └── users │ │ │ ├── models │ │ │ └── User.ts │ │ │ └── routes │ │ │ ├── auth.router.ts │ │ │ └── user.router.ts │ ├── migrations │ │ ├── 20190325-create-feed-item.js │ │ └── 20190328-create-user.js │ ├── sequelize.ts │ └── server.ts ├── tsconfig.json └── tslint.json └── udagram-frontend ├── .eslintrc.json ├── .gitignore ├── angular.json ├── bin └── deploy.sh ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── ionic.config.json ├── package-lock.json ├── package.json ├── src ├── app │ ├── api │ │ ├── api.module.ts │ │ └── api.service.ts │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── auth │ │ ├── auth-login │ │ │ ├── auth-login.component.html │ │ │ ├── auth-login.component.scss │ │ │ └── auth-login.component.ts │ │ ├── auth-menu-button │ │ │ ├── auth-menu-button.component.html │ │ │ ├── auth-menu-button.component.scss │ │ │ ├── auth-menu-button.component.ts │ │ │ └── auth-menu-user │ │ │ │ ├── auth-menu-user.component.html │ │ │ │ ├── auth-menu-user.component.scss │ │ │ │ ├── auth-menu-user.component.spec.ts │ │ │ │ └── auth-menu-user.component.ts │ │ ├── auth-register │ │ │ ├── auth-register.component.html │ │ │ ├── auth-register.component.scss │ │ │ └── auth-register.component.ts │ │ ├── auth.module.ts │ │ ├── models │ │ │ └── user.model.ts │ │ └── services │ │ │ ├── auth.guard.service.ts │ │ │ └── auth.service.ts │ ├── feed │ │ ├── feed-item │ │ │ ├── feed-item.component.html │ │ │ ├── feed-item.component.scss │ │ │ ├── feed-item.component.spec.ts │ │ │ └── feed-item.component.ts │ │ ├── feed-list │ │ │ ├── feed-list.component.html │ │ │ ├── feed-list.component.scss │ │ │ └── feed-list.component.ts │ │ ├── feed-upload │ │ │ ├── feed-upload-button │ │ │ │ ├── feed-upload-button.component.html │ │ │ │ ├── feed-upload-button.component.scss │ │ │ │ └── feed-upload-button.component.ts │ │ │ ├── feed-upload.component.html │ │ │ ├── feed-upload.component.scss │ │ │ └── feed-upload.component.ts │ │ ├── feed.module.ts │ │ ├── models │ │ │ └── feed-item.model.ts │ │ └── services │ │ │ └── feed.provider.service.ts │ ├── home │ │ ├── home.module.ts │ │ ├── home.page.html │ │ ├── home.page.scss │ │ ├── home.page.spec.ts │ │ └── home.page.ts │ └── menubar │ │ ├── menubar.component.html │ │ ├── menubar.component.scss │ │ ├── menubar.component.spec.ts │ │ └── menubar.component.ts ├── assets │ ├── icon │ │ └── favicon.png │ └── shapes.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── global.scss ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── test.ts ├── theme │ └── variables.scss ├── tsconfig.app.json └── tsconfig.spec.json ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | # orgs contain basc recipes and reproducible actions (install node, aws, etc.) 4 | node: circleci/node@5.0.2 5 | eb: circleci/aws-elastic-beanstalk@2.0.1 6 | aws-cli: circleci/aws-cli@3.1.1 7 | # different jobs are calles later in the workflows sections 8 | jobs: 9 | build: 10 | docker: 11 | # the base image can run most needed actions with orbs 12 | - image: "cimg/node:14.15" 13 | steps: 14 | # install node and checkout code 15 | - node/install: 16 | node-version: '14.15' 17 | - checkout 18 | # Use root level package.json to install dependencies in the frontend app 19 | - run: 20 | name: Install Front-End Dependencies 21 | command: | 22 | echo "NODE --version" 23 | echo $(node --version) 24 | echo "NPM --version" 25 | echo $(npm --version) 26 | npm run frontend:install 27 | # TODO: Install dependencies in the the backend API 28 | - run: 29 | name: Install API Dependencies 30 | command: | 31 | echo "TODO: Install dependencies in the the backend API " 32 | # TODO: Lint the frontend 33 | - run: 34 | name: Front-End Lint 35 | command: | 36 | echo "TODO: Lint the frontend" 37 | # TODO: Build the frontend app 38 | - run: 39 | name: Front-End Build 40 | command: | 41 | echo "TODO: Build the frontend app" 42 | # TODO: Build the backend API 43 | - run: 44 | name: API Build 45 | command: | 46 | echo "TODO: Build the backend API" 47 | # deploy step will run only after manual approval 48 | deploy: 49 | docker: 50 | - image: "cimg/base:stable" 51 | # more setup needed for aws, node, elastic beanstalk 52 | steps: 53 | - node/install: 54 | node-version: '14.15' 55 | - eb/setup 56 | - aws-cli/setup 57 | - checkout 58 | - run: 59 | name: Deploy App 60 | # TODO: Install, build, deploy in both apps 61 | command: | 62 | echo "# TODO: Install, build, deploy in both apps" 63 | 64 | workflows: 65 | udagram: 66 | jobs: 67 | - build 68 | - hold: 69 | filters: 70 | branches: 71 | only: 72 | - master 73 | type: approval 74 | requires: 75 | - build 76 | - deploy: 77 | requires: 78 | - hold 79 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | # Workflow to ensure whenever a Github PR is submitted, 2 | # a JIRA ticket gets created automatically. 3 | name: Manual Workflow 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on pull request events but only for the master branch 8 | pull_request_target: 9 | types: [opened, reopened] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | test-transition-issue: 16 | name: Convert Github Issue to Jira Issue 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@master 21 | 22 | - name: Login 23 | uses: atlassian/gajira-login@master 24 | env: 25 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 26 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 27 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 28 | 29 | - name: Create NEW JIRA ticket 30 | id: create 31 | uses: atlassian/gajira-create@master 32 | with: 33 | project: CONUPDATE 34 | issuetype: Task 35 | summary: | 36 | Github PR nd0067 - Full Stack JavaScript Developer | Repo: ${{ github.repository }} | PR# ${{github.event.number}} 37 | description: | 38 | Repo link: https://github.com/${{ github.repository }} 39 | PR no. ${{ github.event.pull_request.number }} 40 | PR title: ${{ github.event.pull_request.title }} 41 | PR description: ${{ github.event.pull_request.description }} 42 | In addition, please resolve other issues, if any. 43 | fields: '{"components": [{"name":"nd0067 - Full Stack JavaScript Developer"}], "customfield_16449":"https://classroom.udacity.com/", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' 44 | 45 | - name: Log created issue 46 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 47 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @udacity/active-public-content -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2012 - 2020, Udacity, Inc. 2 | 3 | Udacity hereby grants you a license in and to the Educational Content, including 4 | but not limited to homework assignments, programming assignments, code samples, 5 | and other educational materials and tools (as further described in the Udacity 6 | Terms of Use), subject to, as modified herein, the terms and conditions of the 7 | Creative Commons Attribution-NonCommercial- NoDerivs 3.0 License located at 8 | http://creativecommons.org/licenses/by-nc-nd/4.0 and successor locations for 9 | such license (the "CC License") provided that, in each case, the Educational 10 | Content is specifically marked as being subject to the CC License. 11 | 12 | Udacity expressly defines the following as falling outside the definition of 13 | "non-commercial": 14 | (a) the sale or rental of (i) any part of the Educational Content, (ii) any 15 | derivative works based at least in part on the Educational Content, or (iii) 16 | any collective work that includes any part of the Educational Content; 17 | (b) the sale of access or a link to any part of the Educational Content without 18 | first obtaining informed consent from the buyer (that the buyer is aware 19 | that the Educational Content, or such part thereof, is available at the 20 | Website free of charge); 21 | (c) providing training, support, or editorial services that use or reference the 22 | Educational Content in exchange for a fee; 23 | (d) the sale of advertisements, sponsorships, or promotions placed on the 24 | Educational Content, or any part thereof, or the sale of advertisements, 25 | sponsorships, or promotions on any website or blog containing any part of 26 | the Educational Material, including without limitation any "pop-up 27 | advertisements"; 28 | (e) the use of Educational Content by a college, university, school, or other 29 | educational institution for instruction where tuition is charged; and 30 | (f) the use of Educational Content by a for-profit corporation or non-profit 31 | entity for internal professional development or training. 32 | 33 | THE SERVICES AND ONLINE COURSES (INCLUDING ANY CONTENT) ARE PROVIDED "AS IS" AND 34 | "AS AVAILABLE" WITH NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, EITHER 35 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. YOU 37 | ASSUME TOTAL RESPONSIBILITY AND THE ENTIRE RISK FOR YOUR USE OF THE SERVICES, 38 | ONLINE COURSES, AND CONTENT. WITHOUT LIMITING THE FOREGOING, WE DO NOT WARRANT 39 | THAT (A) THE SERVICES, WEBSITES, CONTENT, OR THE ONLINE COURSES WILL MEET YOUR 40 | REQUIREMENTS OR EXPECTATIONS OR ACHIEVE THE INTENDED PURPOSES, (B) THE WEBSITES 41 | OR THE ONLINE COURSES WILL NOT EXPERIENCE OUTAGES OR OTHERWISE BE UNINTERRUPTED, 42 | TIMELY, SECURE OR ERROR-FREE, (C) THE INFORMATION OR CONTENT OBTAINED THROUGH 43 | THE SERVICES, SUCH AS CHAT ROOM SERVICES, WILL BE ACCURATE, COMPLETE, CURRENT, 44 | ERROR- FREE, COMPLETELY SECURE OR RELIABLE, OR (D) THAT DEFECTS IN OR ON THE 45 | SERVICES OR CONTENT WILL BE CORRECTED. YOU ASSUME ALL RISK OF PERSONAL INJURY, 46 | INCLUDING DEATH AND DAMAGE TO PERSONAL PROPERTY, SUSTAINED FROM USE OF SERVICES. 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hosting a Full-Stack Application 2 | 3 | ### **You can use you own project completed in previous courses or use the provided Udagram app for completing this final project.** 4 | 5 | --- 6 | 7 | In this project you will learn how to take a newly developed Full-Stack application built for a retailer and deploy it to a cloud service provider so that it is available to customers. You will use the aws console to start and configure the services the application needs such as a database to store product information and a web server allowing the site to be discovered by potential customers. You will modify your package.json scripts and replace hard coded secrets with environment variables in your code. 8 | 9 | After the initial setup, you will learn to interact with the services you started on aws and will deploy manually the application a first time to it. As you get more familiar with the services and interact with them through a CLI, you will gradually understand all the moving parts. 10 | 11 | You will then register for a free account on CircleCi and connect your Github account to it. Based on the manual steps used to deploy the app, you will write a config.yml file that will make the process reproducible in CircleCi. You will set up the process to be executed automatically based when code is pushed on the main Github branch. 12 | 13 | The project will also include writing documentation and runbooks covering the operations of the deployment process. Those runbooks will serve as a way to communicate with future developers and anybody involved in diagnosing outages of the Full-Stack application. 14 | 15 | # Udagram 16 | 17 | This application is provided to you as an alternative starter project if you do not wish to host your own code done in the previous courses of this nanodegree. The udagram application is a fairly simple application that includes all the major components of a Full-Stack web application. 18 | 19 | 20 | 21 | ### Dependencies 22 | 23 | ``` 24 | - Node v14.15.1 (LTS) or more recent. While older versions can work it is advisable to keep node to latest LTS version 25 | 26 | - npm 6.14.8 (LTS) or more recent, Yarn can work but was not tested for this project 27 | 28 | - AWS CLI v2, v1 can work but was not tested for this project 29 | 30 | - A RDS database running Postgres. 31 | 32 | - A S3 bucket for hosting uploaded pictures. 33 | 34 | ``` 35 | 36 | ### Installation 37 | 38 | Provision the necessary AWS services needed for running the application: 39 | 40 | 1. In AWS, provision a publicly available RDS database running Postgres. 41 | 1. In AWS, provision a s3 bucket for hosting the uploaded files. 42 | 1. Export the ENV variables needed or use a package like [dotnev](https://www.npmjs.com/package/dotenv)/. 43 | 1. From the root of the repo, navigate udagram-api folder `cd starter/udagram-api` to install the node_modules `npm install`. After installation is done start the api in dev mode with `npm run dev`. 44 | 1. Without closing the terminal in step 1, navigate to the udagram-frontend `cd starter/udagram-frontend` to intall the node_modules `npm install`. After installation is done start the api in dev mode with `npm run start`. 45 | 46 | ## Testing 47 | 48 | This project contains two different test suite: unit tests and End-To-End tests(e2e). Follow these steps to run the tests. 49 | 50 | 1. `cd starter/udagram-frontend` 51 | 1. `npm run test` 52 | 1. `npm run e2e` 53 | 54 | There are no Unit test on the back-end 55 | 56 | ### Unit Tests: 57 | 58 | Unit tests are using the Jasmine Framework. 59 | 60 | ### End to End Tests: 61 | 62 | The e2e tests are using Protractor and Jasmine. 63 | 64 | ## Built With 65 | 66 | - [Angular](https://angular.io/) - Single Page Application Framework 67 | - [Node](https://nodejs.org) - Javascript Runtime 68 | - [Express](https://expressjs.com/) - Javascript API Framework 69 | 70 | ## License 71 | 72 | [License](LICENSE.txt) 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "frontend:install": "cd udagram/udagram-frontend && npm install -f", 4 | "frontend:start": "cd udagram/udagram-frontend && npm run start", 5 | "frontend:build": "cd udagram/udagram-frontend && npm run build", 6 | "frontend:test": "cd udagram/udagram-frontend && npm run test", 7 | "frontend:e2e": "cd udagram/udagram-frontend && npm run e2e", 8 | "frontend:lint": "cd udagram/udagram-frontend && npm run lint", 9 | "frontend:deploy": "cd udagram/udagram-frontend && npm run deploy", 10 | "api:install": "cd udagram/udagram-api && npm install .", 11 | "api:build": "cd udagram/udagram-api && npm run build", 12 | "api:start": "cd udagram/udagram-api && npm run dev", 13 | "api:deploy": "cd udagram/udagram-api && npm run deploy", 14 | "deploy": "npm run api:deploy && npm run frontend:deploy" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /udagram/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /udagram/set_env.sh: -------------------------------------------------------------------------------- 1 | # This file is used for convenience of local development. 2 | # DO NOT STORE YOUR CREDENTIALS INTO GIT 3 | export POSTGRES_USERNAME=postgres 4 | export POSTGRES_PASSWORD=myPassword 5 | export POSTGRES_HOST=mydbinstance.csxbuclmtj3c.us-east-1.rds.amazonaws.com 6 | export POSTGRES_DB=postgres 7 | export AWS_BUCKET=arn:aws:s3:::myawsbucket-75139724085 8 | export AWS_REGION=us-east-1 9 | export AWS_PROFILE=default 10 | export JWT_SECRET=mysecretstring 11 | export URL=http://localhost:8100 -------------------------------------------------------------------------------- /udagram/udagram-api/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /udagram/udagram-api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "globals": { 13 | "Atomics": "readonly", 14 | "SharedArrayBuffer": "readonly" 15 | }, 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaVersion": 2018, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "@typescript-eslint" 23 | ], 24 | "rules": { 25 | } 26 | } -------------------------------------------------------------------------------- /udagram/udagram-api/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .ionic/ 17 | .sourcemaps/ 18 | .sass-cache/ 19 | .tmp/ 20 | .versions/ 21 | coverage/ 22 | www/ 23 | node_modules/ 24 | tmp/ 25 | temp/ 26 | platforms/ 27 | plugins/ 28 | plugins/android.json 29 | plugins/ios.json 30 | $RECYCLE.BIN/ 31 | postgres_dev/ 32 | logfile 33 | 34 | .DS_Store 35 | Thumbs.db 36 | UserInterfaceState.xcuserstate 37 | node_modules 38 | venv/ 39 | .env 40 | # Elastic Beanstalk Files 41 | .elasticbeanstalk/* 42 | !.elasticbeanstalk/*.cfg.yml 43 | !.elasticbeanstalk/*.global.yml 44 | -------------------------------------------------------------------------------- /udagram/udagram-api/.npmrc: -------------------------------------------------------------------------------- 1 | unsafe-perm=true -------------------------------------------------------------------------------- /udagram/udagram-api/Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /udagram/udagram-api/mock/xander0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-api/mock/xander0.jpg -------------------------------------------------------------------------------- /udagram/udagram-api/mock/xander1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-api/mock/xander1.jpg -------------------------------------------------------------------------------- /udagram/udagram-api/mock/xander2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-api/mock/xander2.jpg -------------------------------------------------------------------------------- /udagram/udagram-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udagram-api", 3 | "version": "2.0.0", 4 | "description": "", 5 | "engines": { 6 | "node": "14.15.0" 7 | }, 8 | "main": "server.ts", 9 | "scripts": { 10 | "start": "node ./server.js", 11 | "start:dev": "node ./www/server.js", 12 | "tsc": "npx tsc", 13 | "dev": "npx ts-node-dev --respawn --transpile-only ./src/server.ts", 14 | "prod": "npx tsc && node ./www/server.js", 15 | "clean": "rm -rf www/ || true", 16 | "deploy": "npm run build && eb list && eb use udagram-api-dev && eb deploy", 17 | "build": "npm install . && npm run clean && tsc && cp -rf src/config www/config && cp -R .elasticbeanstalk www/.elasticbeanstalk && cp .npmrc www/.npmrc && cp package.json www/package.json && cd www && zip -r Archive.zip . && cd ..", 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "keywords": [], 21 | "author": "Gabriel Ruttner", 22 | "license": "ISC", 23 | "dependencies": { 24 | "@types/bcryptjs": "2.4.2", 25 | "@types/jsonwebtoken": "^8.3.2", 26 | "aws-sdk": "^2.429.0", 27 | "bcryptjs": "2.4.3", 28 | "body-parser": "^1.18.3", 29 | "cors": "^2.8.5", 30 | "dotenv": "^8.2.0", 31 | "email-validator": "^2.0.4", 32 | "express": "^4.16.4", 33 | "jsonwebtoken": "^8.5.1", 34 | "pg": "^8.7.1", 35 | "reflect-metadata": "^0.1.13", 36 | "sequelize": "^6.26.0", 37 | "sequelize-typescript": "^2.1.5" 38 | }, 39 | "devDependencies": { 40 | "@types/bluebird": "^3.5.26", 41 | "@types/cors": "^2.8.6", 42 | "@types/express": "^4.16.1", 43 | "@types/node": "^11.11.6", 44 | "@types/sequelize": "^4.28.14", 45 | "@types/validator": "^13.7.10", 46 | "@typescript-eslint/eslint-plugin": "^2.19.2", 47 | "@typescript-eslint/parser": "^2.19.2", 48 | "chai": "^4.2.0", 49 | "chai-http": "^4.2.1", 50 | "eslint": "^6.8.0", 51 | "eslint-config-google": "^0.14.0", 52 | "mocha": "^6.1.4", 53 | "ts-node-dev": "^1.0.0-pre.32", 54 | "typescript": "^4.2.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/aws.ts: -------------------------------------------------------------------------------- 1 | import AWS = require("aws-sdk"); 2 | import { config } from "./config/config"; 3 | 4 | //Credentials are auto set according to the documentation https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html and the default profile is "Default anyway" 5 | 6 | export const s3 = new AWS.S3({ 7 | signatureVersion: "v4", 8 | region: config.aws_region, 9 | params: { Bucket: config.aws_media_bucket }, 10 | }); 11 | 12 | // Generates an AWS signed URL for retrieving objects 13 | export function getGetSignedUrl(key: string): string { 14 | const signedUrlExpireSeconds = 60 * 5; 15 | 16 | return s3.getSignedUrl("getObject", { 17 | Bucket: config.aws_media_bucket, 18 | Key: key, 19 | Expires: signedUrlExpireSeconds, 20 | }); 21 | } 22 | 23 | // Generates an AWS signed URL for uploading objects 24 | export function getPutSignedUrl(key: string): string { 25 | const signedUrlExpireSeconds = 60 * 5; 26 | 27 | return s3.getSignedUrl("putObject", { 28 | Bucket: config.aws_media_bucket, 29 | Key: key, 30 | Expires: signedUrlExpireSeconds, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/config/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Environment Variables Explaination 3 | 4 | You need a separate S3 Media bucket ! 5 | 6 | `POSTGRES_HOST` : Your Postgres DB host 7 | `POSTGRES_USERNAME` : Your Postgres DB username 8 | `POSTGRES_DB` : Your Postgres DB username 9 | `POSTGRES_PASSWORD` : Your Postgres DB username 10 | `PORT` : Currently set to BOTH DB port && application port *Needs fix* 11 | `AWS_REGION` : Your MEDIA bucket AWS region EG.: "eu-west-3" 12 | `AWS_PROFILE` : Set when setting up AWS CLI, by default should be set up to "default" 13 | `AWS_BUCKET` : Your media bucket name EG.: "mediabucket123123" 14 | `URL` : Your backend URL, can be found after creating EB Environment 15 | `JWT_SECRET` : Your JWT token secret, can be set to any value 16 | 17 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | 4 | // ENV variables 5 | // - AWS_ACCESS_KEY_ID 6 | // - AWS_SECRET_ACCESS_KEY 7 | // Are Also needed 8 | 9 | export const config = { 10 | username: `${process.env.POSTGRES_USERNAME}`, 11 | password: process.env.POSTGRES_PASSWORD, 12 | database: process.env.POSTGRES_DB, 13 | host: process.env.POSTGRES_HOST, 14 | aws_region: process.env.AWS_REGION, 15 | aws_profile: process.env.AWS_PROFILE, 16 | aws_media_bucket: process.env.AWS_BUCKET, 17 | url: process.env.URL, 18 | jwt: { 19 | secret: process.env.JWT_SECRET, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/feed/models/FeedItem.ts: -------------------------------------------------------------------------------- 1 | import {Table, Column, Model, CreatedAt, UpdatedAt} from 'sequelize-typescript'; 2 | 3 | 4 | @Table 5 | export class FeedItem extends Model { 6 | @Column 7 | public caption!: string; 8 | 9 | @Column 10 | public url!: string; 11 | 12 | @Column 13 | @CreatedAt 14 | public createdAt: Date = new Date(); 15 | 16 | @Column 17 | @UpdatedAt 18 | public updatedAt: Date = new Date(); 19 | } 20 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/feed/routes/feed.router.ts: -------------------------------------------------------------------------------- 1 | import {Router, Request, Response} from 'express'; 2 | import {FeedItem} from '../models/FeedItem'; 3 | import {NextFunction} from 'connect'; 4 | import * as jwt from 'jsonwebtoken'; 5 | import * as AWS from '../../../../aws'; 6 | import * as c from '../../../../config/config'; 7 | 8 | const router: Router = Router(); 9 | 10 | export function requireAuth(req: Request, res: Response, next: NextFunction) { 11 | if (!req.headers || !req.headers.authorization) { 12 | return res.status(401).send({message: 'No authorization headers.'}); 13 | } 14 | 15 | const tokenBearer = req.headers.authorization.split(' '); 16 | if (tokenBearer.length != 2) { 17 | return res.status(401).send({message: 'Malformed token.'}); 18 | } 19 | 20 | const token = tokenBearer[1]; 21 | return jwt.verify(token, c.config.jwt.secret, (err, decoded) => { 22 | if (err) { 23 | return res.status(500).send({auth: false, message: 'Failed to authenticate.'}); 24 | } 25 | return next(); 26 | }); 27 | } 28 | 29 | // Get all feed items 30 | router.get('/', async (req: Request, res: Response) => { 31 | const items = await FeedItem.findAndCountAll({order: [['id', 'DESC']]}); 32 | items.rows.map((item) => { 33 | if (item.url) { 34 | item.url = AWS.getGetSignedUrl(item.url); 35 | } 36 | }); 37 | res.send(items); 38 | }); 39 | 40 | // Get a feed resource 41 | router.get('/:id', 42 | async (req: Request, res: Response) => { 43 | const {id} = req.params; 44 | const item = await FeedItem.findByPk(id); 45 | res.send(item); 46 | }); 47 | 48 | // Get a signed url to put a new item in the bucket 49 | router.get('/signed-url/:fileName', 50 | requireAuth, 51 | async (req: Request, res: Response) => { 52 | const {fileName} = req.params; 53 | const url = AWS.getPutSignedUrl(fileName); 54 | res.status(201).send({url: url}); 55 | }); 56 | 57 | // Create feed with metadata 58 | router.post('/', 59 | requireAuth, 60 | async (req: Request, res: Response) => { 61 | const caption = req.body.caption; 62 | const fileName = req.body.url; // same as S3 key name 63 | 64 | if (!caption) { 65 | return res.status(400).send({message: 'Caption is required or malformed.'}); 66 | } 67 | 68 | if (!fileName) { 69 | return res.status(400).send({message: 'File url is required.'}); 70 | } 71 | 72 | //@ts-ignore 73 | const item = await new FeedItem({ 74 | caption: caption, 75 | url: fileName, 76 | }); 77 | 78 | const savedItem = await item.save(); 79 | 80 | savedItem.url = AWS.getGetSignedUrl(savedItem.url); 81 | res.status(201).send(savedItem); 82 | }); 83 | 84 | export const FeedRouter: Router = router; 85 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/index.router.ts: -------------------------------------------------------------------------------- 1 | import {Router, Request, Response} from 'express'; 2 | import {FeedRouter} from './feed/routes/feed.router'; 3 | import {UserRouter} from './users/routes/user.router'; 4 | 5 | const router: Router = Router(); 6 | 7 | router.use('/feed', FeedRouter); 8 | router.use('/users', UserRouter); 9 | 10 | router.get('/', async (req: Request, res: Response) => { 11 | res.send(`V0`); 12 | }); 13 | 14 | export const IndexRouter: Router = router; 15 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/model.index.ts: -------------------------------------------------------------------------------- 1 | import {FeedItem} from './feed/models/FeedItem'; 2 | import {User} from './users/models/User'; 3 | 4 | 5 | export const V0_USER_MODELS = [User]; 6 | export const V0_FEED_MODELS = [FeedItem]; 7 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/users/models/User.ts: -------------------------------------------------------------------------------- 1 | import {Table, Column, Model, PrimaryKey, CreatedAt, UpdatedAt} from 'sequelize-typescript'; 2 | 3 | @Table 4 | export class User extends Model { 5 | @PrimaryKey 6 | @Column 7 | public email!: string; 8 | 9 | @Column 10 | public passwordHash!: string; 11 | 12 | @Column 13 | @CreatedAt 14 | public createdAt: Date = new Date(); 15 | 16 | @Column 17 | @UpdatedAt 18 | public updatedAt: Date = new Date(); 19 | 20 | short() { 21 | return { 22 | email: this.email, 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/users/routes/auth.router.ts: -------------------------------------------------------------------------------- 1 | import {Router, Request, Response} from 'express'; 2 | 3 | import {User} from '../models/User'; 4 | import * as c from '../../../../config/config'; 5 | 6 | // import * as bcrypt from 'bcrypt'; 7 | import * as jwt from 'jsonwebtoken'; 8 | import {NextFunction} from 'connect'; 9 | 10 | import * as EmailValidator from 'email-validator'; 11 | import {config} from 'bluebird'; 12 | 13 | const router: Router = Router(); 14 | var bcrypt = require('bcryptjs'); 15 | 16 | async function generatePassword(plainTextPassword: string): Promise { 17 | const saltRounds = 10; 18 | const salt = await bcrypt.genSalt(saltRounds); 19 | return await bcrypt.hash(plainTextPassword, salt); 20 | } 21 | 22 | async function comparePasswords(plainTextPassword: string, hash: string): Promise { 23 | return await bcrypt.compare(plainTextPassword, hash); 24 | } 25 | 26 | function generateJWT(user: User): string { 27 | return jwt.sign(user.short(), c.config.jwt.secret); 28 | } 29 | 30 | export function requireAuth(req: Request, res: Response, next: NextFunction) { 31 | if (!req.headers || !req.headers.authorization) { 32 | return res.status(401).send({message: 'No authorization headers.'}); 33 | } 34 | 35 | const tokenBearer = req.headers.authorization.split(' '); 36 | if (tokenBearer.length != 2) { 37 | return res.status(401).send({message: 'Malformed token.'}); 38 | } 39 | 40 | const token = tokenBearer[1]; 41 | return jwt.verify(token, c.config.jwt.secret, (err, decoded) => { 42 | if (err) { 43 | return res.status(500).send({auth: false, message: 'Failed to authenticate.'}); 44 | } 45 | return next(); 46 | }); 47 | } 48 | 49 | router.get('/verification', 50 | requireAuth, 51 | async (req: Request, res: Response) => { 52 | return res.status(200).send({auth: true, message: 'Authenticated.'}); 53 | }); 54 | 55 | router.post('/login', async (req: Request, res: Response) => { 56 | const email = req.body.email; 57 | const password = req.body.password; 58 | 59 | if (!email || !EmailValidator.validate(email)) { 60 | return res.status(400).send({auth: false, message: 'Email is required or malformed.'}); 61 | } 62 | 63 | if (!password) { 64 | return res.status(400).send({auth: false, message: 'Password is required.'}); 65 | } 66 | 67 | const user = await User.findByPk(email); 68 | if (!user) { 69 | return res.status(401).send({auth: false, message: 'User was not found..'}); 70 | } 71 | 72 | const authValid = await comparePasswords(password, user.passwordHash); 73 | 74 | if (!authValid) { 75 | return res.status(401).send({auth: false, message: 'Password was invalid.'}); 76 | } 77 | 78 | const jwt = generateJWT(user); 79 | res.status(200).send({auth: true, token: jwt, user: user.short()}); 80 | }); 81 | 82 | 83 | router.post('/', async (req: Request, res: Response) => { 84 | const email = req.body.email; 85 | const plainTextPassword = req.body.password; 86 | 87 | if (!email || !EmailValidator.validate(email)) { 88 | return res.status(400).send({auth: false, message: 'Email is missing or malformed.'}); 89 | } 90 | 91 | if (!plainTextPassword) { 92 | return res.status(400).send({auth: false, message: 'Password is required.'}); 93 | } 94 | 95 | const user = await User.findByPk(email); 96 | if (user) { 97 | return res.status(422).send({auth: false, message: 'User already exists.'}); 98 | } 99 | 100 | const generatedHash = await generatePassword(plainTextPassword); 101 | 102 | //@ts-ignore 103 | const newUser = await new User({ 104 | email: email, 105 | passwordHash: generatedHash, 106 | }); 107 | 108 | const savedUser = await newUser.save(); 109 | 110 | 111 | const jwt = generateJWT(savedUser); 112 | res.status(201).send({token: jwt, user: savedUser.short()}); 113 | }); 114 | 115 | router.get('/', async (req: Request, res: Response) => { 116 | res.send('auth'); 117 | }); 118 | 119 | export const AuthRouter: Router = router; 120 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/controllers/v0/users/routes/user.router.ts: -------------------------------------------------------------------------------- 1 | import {Router, Request, Response} from 'express'; 2 | 3 | import {User} from '../models/User'; 4 | import {AuthRouter} from './auth.router'; 5 | 6 | const router: Router = Router(); 7 | 8 | router.use('/auth', AuthRouter); 9 | 10 | router.get('/'); 11 | 12 | router.get('/:id', async (req: Request, res: Response) => { 13 | const {id} = req.params; 14 | const item = await User.findByPk(id); 15 | res.send(item); 16 | }); 17 | 18 | export const UserRouter: Router = router; 19 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/migrations/20190325-create-feed-item.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('FeedItem', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | caption: { 12 | type: Sequelize.STRING, 13 | }, 14 | url: { 15 | type: Sequelize.STRING, 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE, 24 | }, 25 | }); 26 | }, 27 | down: (queryInterface, Sequelize) => { 28 | return queryInterface.dropTable('FeedItem'); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/migrations/20190328-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('User', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | email: { 11 | type: Sequelize.STRING, 12 | primaryKey: true, 13 | }, 14 | passwordHash: { 15 | type: Sequelize.STRING, 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE, 24 | }, 25 | }); 26 | }, 27 | down: (queryInterface, Sequelize) => { 28 | return queryInterface.dropTable('User'); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from "sequelize-typescript"; 2 | import { config } from "./config/config"; 3 | 4 | export const sequelize = new Sequelize({ 5 | username: config.username, 6 | password: config.password, 7 | database: config.database, 8 | host: config.host, 9 | 10 | dialect: "postgres", 11 | storage: ":memory:", 12 | }); 13 | -------------------------------------------------------------------------------- /udagram/udagram-api/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import cors from 'cors'; 3 | import express from "express"; 4 | import { sequelize } from "./sequelize"; 5 | 6 | import { IndexRouter } from "./controllers/v0/index.router"; 7 | 8 | import bodyParser from "body-parser"; 9 | import { V0_FEED_MODELS, V0_USER_MODELS } from "./controllers/v0/model.index"; 10 | 11 | (async () => { 12 | dotenv.config(); 13 | 14 | try { 15 | await sequelize.authenticate(); 16 | console.log("Connection has been established successfully."); 17 | } catch (error) { 18 | console.error("Unable to connect to the database:", error); 19 | } 20 | 21 | await sequelize.addModels(V0_FEED_MODELS); 22 | await sequelize.addModels(V0_USER_MODELS); 23 | await sequelize.sync(); 24 | 25 | console.log("Database Connected"); 26 | 27 | const app = express(); 28 | const port = 8080; 29 | 30 | app.use(bodyParser.json()); 31 | 32 | // app.use(cors()); 33 | // We set the CORS origin to * so that we don't need to 34 | // worry about the complexities of CORS. 35 | app.use(cors({ 36 | "allowedHeaders": [ 37 | 'Origin', 'X-Requested-With', 38 | 'Content-Type', 'Accept', 39 | 'X-Access-Token', 'Authorization', 'Access-Control-Allow-Origin', 40 | 'Access-Control-Allow-Headers', 41 | 'Access-Control-Allow-Methods' 42 | ], 43 | "methods": 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE', 44 | "preflightContinue": true, 45 | "origin": '*', 46 | })); 47 | 48 | app.use("/api/v0/", IndexRouter); 49 | 50 | // Root URI call 51 | app.get("/", async (req, res) => { 52 | res.send("/api/v0/"); 53 | }); 54 | 55 | // Start the Server 56 | app.listen(port, () => { 57 | console.log(`Backend server is listening on port ${port}....`); 58 | console.log(`Frontent server running ${process.env.URL}`); 59 | console.log(`press CTRL+C to stop server`); 60 | }); 61 | })(); 62 | -------------------------------------------------------------------------------- /udagram/udagram-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./www", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | "strictNullChecks": false, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | "baseUrl": "/", /* Base directory to resolve non-absolute module names. */ 42 | "paths": { 43 | "*": [ 44 | "node_modules/*" 45 | ] 46 | }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | 60 | /* Experimental Options */ 61 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 62 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 63 | "skipLibCheck": true 64 | }, 65 | "include": [ 66 | "src/**/*" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /udagram/udagram-api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-spacing": true, 20 | "indent": [ 21 | true, 22 | "spaces" 23 | ], 24 | "interface-over-type-literal": true, 25 | "label-position": true, 26 | "max-line-length": [ 27 | true, 28 | 140 29 | ], 30 | "member-access": false, 31 | "member-ordering": [ 32 | true, 33 | { 34 | "order": [ 35 | "static-field", 36 | "instance-field", 37 | "static-method", 38 | "instance-method" 39 | ] 40 | } 41 | ], 42 | "no-arg": true, 43 | "no-bitwise": true, 44 | "no-console": [ 45 | true, 46 | "debug", 47 | "info", 48 | "time", 49 | "timeEnd", 50 | "trace" 51 | ], 52 | "no-construct": true, 53 | "no-debugger": true, 54 | "no-duplicate-super": true, 55 | "no-empty": false, 56 | "no-empty-interface": true, 57 | "no-eval": true, 58 | "no-inferrable-types": [ 59 | true, 60 | "ignore-params" 61 | ], 62 | "no-misused-new": true, 63 | "no-non-null-assertion": true, 64 | "no-shadowed-variable": true, 65 | "no-string-literal": false, 66 | "no-string-throw": true, 67 | "no-switch-case-fall-through": true, 68 | "no-trailing-whitespace": true, 69 | "no-unnecessary-initializer": true, 70 | "no-unused-expression": true, 71 | "no-use-before-declare": true, 72 | "no-var-keyword": true, 73 | "object-literal-sort-keys": false, 74 | "one-line": [ 75 | true, 76 | "check-open-brace", 77 | "check-catch", 78 | "check-else", 79 | "check-whitespace" 80 | ], 81 | "prefer-const": true, 82 | "quotemark": [ 83 | true, 84 | "single" 85 | ], 86 | "radix": true, 87 | "semicolon": [ 88 | true, 89 | "always" 90 | ], 91 | "triple-equals": [ 92 | true, 93 | "allow-null-check" 94 | ], 95 | "typedef-whitespace": [ 96 | true, 97 | { 98 | "call-signature": "nospace", 99 | "index-signature": "nospace", 100 | "parameter": "nospace", 101 | "property-declaration": "nospace", 102 | "variable-declaration": "nospace" 103 | } 104 | ], 105 | "unified-signatures": true, 106 | "variable-name": false, 107 | "whitespace": [ 108 | true, 109 | "check-branch", 110 | "check-decl", 111 | "check-operator", 112 | "check-separator", 113 | "check-type" 114 | ], 115 | "directive-selector": [ 116 | true, 117 | "attribute", 118 | "app", 119 | "camelCase" 120 | ], 121 | "component-selector": [ 122 | true, 123 | "element", 124 | "app", 125 | "page", 126 | "kebab-case" 127 | ], 128 | "no-output-on-prefix": true, 129 | "use-input-property-decorator": true, 130 | "use-output-property-decorator": true, 131 | "use-host-property-decorator": true, 132 | "no-input-rename": true, 133 | "no-output-rename": true, 134 | "use-life-cycle-interface": true, 135 | "use-pipe-transform-interface": true, 136 | "directive-class-suffix": true 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaVersion": 2018, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "@typescript-eslint" 22 | ], 23 | "rules": { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .ionic/ 17 | .sourcemaps/ 18 | .sass-cache/ 19 | .tmp/ 20 | .versions/ 21 | coverage/ 22 | www/ 23 | node_modules/ 24 | tmp/ 25 | temp/ 26 | platforms/ 27 | plugins/ 28 | plugins/android.json 29 | plugins/ios.json 30 | $RECYCLE.BIN/ 31 | 32 | .DS_Store 33 | Thumbs.db 34 | UserInterfaceState.xcuserstate 35 | node_modules 36 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", 3 | "version": 1, 4 | "defaultProject": "app", 5 | "newProjectRoot": "projects", 6 | "projects": { 7 | "app": { 8 | "root": "", 9 | "sourceRoot": "src", 10 | "projectType": "application", 11 | "prefix": "app", 12 | "schematics": {}, 13 | "architect": { 14 | "build": { 15 | "builder": "@angular-devkit/build-angular:browser", 16 | "options": { 17 | "outputPath": "www", 18 | "index": "src/index.html", 19 | "main": "src/main.ts", 20 | "polyfills": "src/polyfills.ts", 21 | "tsConfig": "src/tsconfig.app.json", 22 | "assets": [ 23 | { 24 | "glob": "**/*", 25 | "input": "src/assets", 26 | "output": "assets" 27 | }, 28 | { 29 | "glob": "**/*.svg", 30 | "input": "node_modules/ionicons/dist/ionicons/svg", 31 | "output": "./svg" 32 | } 33 | ], 34 | "styles": [ 35 | { 36 | "input": "src/theme/variables.scss" 37 | }, 38 | { 39 | "input": "src/global.scss" 40 | } 41 | ], 42 | "scripts": [], 43 | "es5BrowserSupport": true 44 | }, 45 | "configurations": { 46 | "production": { 47 | "fileReplacements": [ 48 | { 49 | "replace": "src/environments/environment.ts", 50 | "with": "src/environments/environment.prod.ts" 51 | } 52 | ], 53 | "optimization": true, 54 | "outputHashing": "all", 55 | "sourceMap": false, 56 | "extractCss": true, 57 | "namedChunks": false, 58 | "aot": true, 59 | "extractLicenses": true, 60 | "vendorChunk": false, 61 | "buildOptimizer": true, 62 | "budgets": [ 63 | { 64 | "type": "initial", 65 | "maximumWarning": "2mb", 66 | "maximumError": "5mb" 67 | } 68 | ] 69 | }, 70 | "ci": { 71 | "progress": false 72 | } 73 | } 74 | }, 75 | "serve": { 76 | "builder": "@angular-devkit/build-angular:dev-server", 77 | "options": { 78 | "browserTarget": "app:build" 79 | }, 80 | "configurations": { 81 | "production": { 82 | "browserTarget": "app:build:production" 83 | }, 84 | "ci": { 85 | "progress": false 86 | } 87 | } 88 | }, 89 | "extract-i18n": { 90 | "builder": "@angular-devkit/build-angular:extract-i18n", 91 | "options": { 92 | "browserTarget": "app:build" 93 | } 94 | }, 95 | "test": { 96 | "builder": "@angular-devkit/build-angular:karma", 97 | "options": { 98 | "main": "src/test.ts", 99 | "polyfills": "src/polyfills.ts", 100 | "tsConfig": "src/tsconfig.spec.json", 101 | "karmaConfig": "src/karma.conf.js", 102 | "styles": [], 103 | "scripts": [], 104 | "assets": [ 105 | { 106 | "glob": "favicon.ico", 107 | "input": "src/", 108 | "output": "/" 109 | }, 110 | { 111 | "glob": "**/*", 112 | "input": "src/assets", 113 | "output": "/assets" 114 | } 115 | ] 116 | }, 117 | "configurations": { 118 | "ci": { 119 | "progress": false, 120 | "watch": false 121 | } 122 | } 123 | }, 124 | "lint": { 125 | "builder": "@angular-devkit/build-angular:tslint", 126 | "options": { 127 | "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], 128 | "exclude": ["**/node_modules/**"] 129 | } 130 | }, 131 | "ionic-cordova-build": { 132 | "builder": "@ionic/angular-toolkit:cordova-build", 133 | "options": { 134 | "browserTarget": "app:build" 135 | }, 136 | "configurations": { 137 | "production": { 138 | "browserTarget": "app:build:production" 139 | } 140 | } 141 | }, 142 | "ionic-cordova-serve": { 143 | "builder": "@ionic/angular-toolkit:cordova-serve", 144 | "options": { 145 | "cordovaBuildTarget": "app:ionic-cordova-build", 146 | "devServerTarget": "app:serve" 147 | }, 148 | "configurations": { 149 | "production": { 150 | "cordovaBuildTarget": "app:ionic-cordova-build:production", 151 | "devServerTarget": "app:serve:production" 152 | } 153 | } 154 | } 155 | } 156 | }, 157 | "app-e2e": { 158 | "root": "e2e/", 159 | "projectType": "application", 160 | "architect": { 161 | "e2e": { 162 | "builder": "@angular-devkit/build-angular:protractor", 163 | "options": { 164 | "protractorConfig": "e2e/protractor.conf.js", 165 | "devServerTarget": "app:serve" 166 | }, 167 | "configurations": { 168 | "ci": { 169 | "devServerTarget": "app:serve:ci" 170 | } 171 | } 172 | }, 173 | "lint": { 174 | "builder": "@angular-devkit/build-angular:tslint", 175 | "options": { 176 | "tsConfig": "e2e/tsconfig.e2e.json", 177 | "exclude": ["**/node_modules/**"] 178 | } 179 | } 180 | } 181 | } 182 | }, 183 | "cli": { 184 | "defaultCollection": "@ionic/angular-toolkit" 185 | }, 186 | "schematics": { 187 | "@ionic/angular-toolkit:component": { 188 | "styleext": "scss" 189 | }, 190 | "@ionic/angular-toolkit:page": { 191 | "styleext": "scss" 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/bin/deploy.sh: -------------------------------------------------------------------------------- 1 | aws s3 cp --recursive --acl public-read ./www s3://myawsbucket-75139724085/ 2 | aws s3 cp --acl public-read --cache-control="max-age=0, no-cache, no-store, must-revalidate" ./www/index.html s3://myawsbucket-75139724085/ -------------------------------------------------------------------------------- /udagram/udagram-frontend/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('new App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | describe('default screen', () => { 10 | beforeEach(() => { 11 | page.navigateTo('/home'); 12 | }); 13 | it('should have a title saying Home', () => { 14 | page.getPageOneTitleText().then(title => { 15 | expect(title).toEqual('Home'); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(destination) { 5 | return browser.get(destination); 6 | } 7 | 8 | getTitle() { 9 | return browser.getTitle(); 10 | } 11 | 12 | getPageOneTitleText() { 13 | return element(by.tagName('app-home')).element(by.deepCss('ion-title')).getText(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udacity-c2-frontend", 3 | "integrations": {}, 4 | "type": "angular" 5 | } 6 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udagram-frontend", 3 | "version": "0.0.1", 4 | "author": "Ionic Framework", 5 | "homepage": "https://ionicframework.com/", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "deploy": "npm install -f && npm run build && chmod +x bin/deploy.sh && bin/deploy.sh", 11 | "test": "ng test --watch=false", 12 | "lint": "ng lint", 13 | "e2e": "ng e2e" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/common": "^8.2.14", 18 | "@angular/core": "^8.2.14", 19 | "@angular/forms": "^8.2.14", 20 | "@angular/http": "^7.2.16", 21 | "@angular/platform-browser": "^8.2.14", 22 | "@angular/platform-browser-dynamic": "^8.2.14", 23 | "@angular/router": "^8.2.14", 24 | "@ionic-native/core": "^5.0.0", 25 | "@ionic-native/splash-screen": "^5.0.0", 26 | "@ionic-native/status-bar": "^5.0.0", 27 | "@ionic/angular": "^4.1.0", 28 | "core-js": "^2.5.4", 29 | "rxjs": "~6.5.4", 30 | "zone.js": "~0.9.1" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/architect": "~0.12.3", 34 | "@angular-devkit/build-angular": "^0.803.24", 35 | "@angular-devkit/core": "~7.2.3", 36 | "@angular-devkit/schematics": "~7.2.3", 37 | "@angular/cli": "~8.3.25", 38 | "@angular/compiler": "~8.2.14", 39 | "@angular/compiler-cli": "~8.2.14", 40 | "@angular/language-service": "~8.2.14", 41 | "@ionic/angular-toolkit": "~1.4.0", 42 | "@types/jasmine": "~2.8.8", 43 | "@types/jasminewd2": "~2.0.3", 44 | "@types/node": "~10.12.0", 45 | "@typescript-eslint/eslint-plugin": "^2.20.0", 46 | "@typescript-eslint/parser": "^2.20.0", 47 | "codelyzer": "~4.5.0", 48 | "jasmine-core": "~2.99.1", 49 | "jasmine-spec-reporter": "~4.2.1", 50 | "karma": "~3.1.4", 51 | "karma-chrome-launcher": "~2.2.0", 52 | "karma-coverage-istanbul-reporter": "~2.0.1", 53 | "karma-jasmine": "~1.1.2", 54 | "karma-jasmine-html-reporter": "^0.2.2", 55 | "protractor": "~5.4.0", 56 | "ts-node": "~8.0.0", 57 | "tslint": "~5.12.0", 58 | "typescript": "^3.5.3" 59 | }, 60 | "description": "An Ionic project" 61 | } 62 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/api/api.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | 4 | 5 | const components = []; 6 | 7 | @NgModule({ 8 | imports: [ 9 | HttpClientModule, 10 | ], 11 | declarations: components, 12 | exports: components, 13 | providers: [] 14 | }) 15 | export class ApiModule {} 16 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/api/api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; 3 | import { environment } from '../../environments/environment'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | const API_HOST = environment.apiHost; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class ApiService { 12 | httpOptions = { 13 | headers: new HttpHeaders({'Content-Type': 'application/json'}) 14 | }; 15 | 16 | token: string; 17 | 18 | constructor(private http: HttpClient) { 19 | } 20 | 21 | static handleError(error: Error) { 22 | alert(error.message); 23 | } 24 | 25 | static extractData(res: HttpEvent) { 26 | const body = res; 27 | return body || { }; 28 | } 29 | 30 | setAuthToken(token) { 31 | this.httpOptions.headers = this.httpOptions.headers.append('Authorization', `jwt ${token}`); 32 | this.token = token; 33 | } 34 | 35 | get(endpoint): Promise { 36 | const url = `${API_HOST}${endpoint}`; 37 | const req = this.http.get(url, this.httpOptions).pipe(map(ApiService.extractData)); 38 | 39 | return req 40 | .toPromise() 41 | .catch((e) => { 42 | ApiService.handleError(e); 43 | throw e; 44 | }); 45 | } 46 | 47 | post(endpoint, data): Promise { 48 | const url = `${API_HOST}${endpoint}`; 49 | return this.http.post>(url, data, this.httpOptions) 50 | .toPromise() 51 | .catch((e) => { 52 | ApiService.handleError(e); 53 | throw e; 54 | }); 55 | } 56 | 57 | async upload(endpoint: string, file: File, payload: any): Promise { 58 | const signed_url = (await this.get(`${endpoint}/signed-url/${file.name}`)).url; 59 | 60 | const headers = new HttpHeaders({'Content-Type': file.type}); 61 | const req = new HttpRequest( 'PUT', signed_url, file, 62 | { 63 | headers: headers, 64 | reportProgress: true, // track progress 65 | }); 66 | 67 | return new Promise ( resolve => { 68 | this.http.request(req).subscribe((resp) => { 69 | if (resp && ( resp).status && ( resp).status === 200) { 70 | resolve(this.post(endpoint, payload)); 71 | } 72 | }); 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | redirectTo: 'home', 8 | pathMatch: 'full' 9 | }, 10 | { 11 | path: 'home', 12 | loadChildren: './home/home.module#HomePageModule' 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) 19 | ], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule {} 23 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Menu 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{p.title}} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | 4 | import { Platform } from '@ionic/angular'; 5 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 6 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | 9 | import { AppComponent } from './app.component'; 10 | 11 | describe('AppComponent', () => { 12 | 13 | let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy; 14 | 15 | beforeEach(async(() => { 16 | statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']); 17 | splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']); 18 | platformReadySpy = Promise.resolve(); 19 | platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy }); 20 | 21 | TestBed.configureTestingModule({ 22 | declarations: [AppComponent], 23 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 24 | providers: [ 25 | { provide: StatusBar, useValue: statusBarSpy }, 26 | { provide: SplashScreen, useValue: splashScreenSpy }, 27 | { provide: Platform, useValue: platformSpy }, 28 | ], 29 | imports: [ RouterTestingModule.withRoutes([])], 30 | }).compileComponents(); 31 | })); 32 | 33 | it('should create the app', async () => { 34 | const fixture = TestBed.createComponent(AppComponent); 35 | const app = fixture.debugElement.componentInstance; 36 | expect(app).toBeTruthy(); 37 | }); 38 | 39 | it('should initialize the app', async () => { 40 | TestBed.createComponent(AppComponent); 41 | expect(platformSpy.ready).toHaveBeenCalled(); 42 | await platformReadySpy; 43 | expect(statusBarSpy.styleDefault).toHaveBeenCalled(); 44 | expect(splashScreenSpy.hide).toHaveBeenCalled(); 45 | }); 46 | 47 | it('should have menu labels', async () => { 48 | const fixture = await TestBed.createComponent(AppComponent); 49 | await fixture.detectChanges(); 50 | const app = fixture.nativeElement; 51 | const menuItems = app.querySelectorAll('ion-label'); 52 | expect(menuItems.length).toEqual(1); 53 | expect(menuItems[0].textContent).toContain('Home'); 54 | }); 55 | 56 | it('should have urls', async () => { 57 | const fixture = await TestBed.createComponent(AppComponent); 58 | await fixture.detectChanges(); 59 | const app = fixture.nativeElement; 60 | const menuItems = app.querySelectorAll('ion-item'); 61 | expect(menuItems.length).toEqual(1); 62 | expect(menuItems[0].getAttribute('ng-reflect-router-link')).toEqual('/home'); 63 | }); 64 | 65 | it('should have one router outlet', async () => { 66 | const fixture = await TestBed.createComponent(AppComponent); 67 | await fixture.detectChanges(); 68 | const app = fixture.nativeElement; 69 | const routerOutlet = app.querySelectorAll('ion-router-outlet'); 70 | expect(routerOutlet.length).toEqual(1); 71 | }); 72 | 73 | it('should have one menubar', async () => { 74 | const fixture = await TestBed.createComponent(AppComponent); 75 | await fixture.detectChanges(); 76 | const app = fixture.nativeElement; 77 | const menubar = app.querySelectorAll('app-menubar'); 78 | expect(menubar.length).toEqual(1); 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { Platform } from '@ionic/angular'; 4 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 5 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 6 | import { environment } from '../environments/environment'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: 'app.component.html' 11 | }) 12 | export class AppComponent { 13 | public appPages = [ 14 | { 15 | title: 'Home', 16 | url: '/home', 17 | icon: 'home' 18 | } 19 | ]; 20 | 21 | public appName = environment.appName; 22 | 23 | constructor( 24 | private platform: Platform, 25 | private splashScreen: SplashScreen, 26 | private statusBar: StatusBar 27 | ) { 28 | this.initializeApp(); 29 | } 30 | 31 | initializeApp() { 32 | this.platform.ready().then(() => { 33 | this.statusBar.styleDefault(); 34 | this.splashScreen.hide(); 35 | document.title = environment.appName; 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouteReuseStrategy } from '@angular/router'; 4 | 5 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; 6 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 7 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { MenubarComponent } from './menubar/menubar.component'; 12 | 13 | import { AuthModule } from './auth/auth.module'; 14 | import { ApiService } from './api/api.service'; 15 | 16 | @NgModule({ 17 | declarations: [ 18 | AppComponent, 19 | MenubarComponent 20 | ], 21 | entryComponents: [], 22 | imports: [ 23 | BrowserModule, 24 | IonicModule.forRoot(), 25 | AppRoutingModule, 26 | AuthModule 27 | ], 28 | providers: [ 29 | ApiService, 30 | StatusBar, 31 | SplashScreen, 32 | { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } 33 | ], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule {} 37 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-login/auth-login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Email 4 | 5 | 6 | 7 | Password 8 | 9 | 10 | 14 | Log In 15 | 16 | {{error}} 17 |
18 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-login/auth-login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/auth/auth-login/auth-login.component.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-login/auth-login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Validators, FormBuilder, FormGroup, FormControl } from '@angular/forms'; 3 | 4 | import { ModalController } from '@ionic/angular'; 5 | 6 | import { AuthService } from '../services/auth.service'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-auth-login', 11 | templateUrl: './auth-login.component.html', 12 | styleUrls: ['./auth-login.component.scss'], 13 | }) 14 | export class AuthLoginComponent implements OnInit { 15 | loginForm: FormGroup; 16 | error: string; 17 | 18 | constructor( 19 | private formBuilder: FormBuilder, 20 | private auth: AuthService, 21 | private modal: ModalController 22 | ) { } 23 | 24 | ngOnInit() { 25 | this.loginForm = this.formBuilder.group({ 26 | password: new FormControl('', Validators.required), 27 | email: new FormControl('', Validators.compose([ 28 | Validators.required, 29 | Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$') 30 | ])) 31 | }); 32 | } 33 | 34 | async onSubmit($event) { 35 | $event.preventDefault(); 36 | 37 | if (!this.loginForm.valid) { return; } 38 | 39 | this.auth.login( 40 | this.loginForm.controls.email.value, 41 | this.loginForm.controls.password.value) 42 | .then((user) => { 43 | this.modal.dismiss(); 44 | }) 45 | .catch((e) => { 46 | this.error = e.statusText; 47 | throw e; 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | Register 8 | 9 | 12 | Log In 13 | 14 | 15 | 16 | 17 | 19 | 21 | {{( auth.currentUser$ | async ).email }} 22 | 23 | 26 | Log Out 27 | 28 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-button.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | ion-avatar { 9 | width: 35px; 10 | height: 35px; 11 | } -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | import { AuthMenuUserComponent } from './auth-menu-user/auth-menu-user.component'; 4 | 5 | import { AuthService } from '../services/auth.service'; 6 | import { AuthLoginComponent } from '../auth-login/auth-login.component'; 7 | import { AuthRegisterComponent } from '../auth-register/auth-register.component'; 8 | 9 | @Component({ 10 | selector: 'app-auth-menu-button', 11 | templateUrl: './auth-menu-button.component.html', 12 | styleUrls: ['./auth-menu-button.component.scss'], 13 | }) 14 | export class AuthMenuButtonComponent implements OnInit { 15 | 16 | constructor( 17 | private auth: AuthService, 18 | public modalController: ModalController 19 | ) {} 20 | 21 | async presentmodal(ev: any) { 22 | const modal = await this.modalController.create({ 23 | component: AuthMenuUserComponent, 24 | }); 25 | return await modal.present(); 26 | } 27 | 28 | async presentLogin(ev: any) { 29 | const modal = await this.modalController.create({ 30 | component: AuthLoginComponent, 31 | }); 32 | return await modal.present(); 33 | } 34 | 35 | async presentRegister(ev: any) { 36 | const modal = await this.modalController.create({ 37 | component: AuthRegisterComponent, 38 | }); 39 | return await modal.present(); 40 | } 41 | 42 | logout() { 43 | this.auth.logout(); 44 | } 45 | 46 | ngOnInit() {} 47 | 48 | } 49 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-user/auth-menu-user.component.html: -------------------------------------------------------------------------------- 1 | Dismiss 2 | 3 |

4 | auth-menu-user works! 5 |

6 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-user/auth-menu-user.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-user/auth-menu-user.component.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-user/auth-menu-user.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { AuthMenuUserComponent } from './auth-menu-user.component'; 5 | import { ModalController } from '@ionic/angular'; 6 | 7 | describe('AuthMenuUserPage', () => { 8 | let component: AuthMenuUserComponent; 9 | let fixture: ComponentFixture; 10 | let modalSpy; 11 | let modalCtrlSpy; 12 | 13 | beforeEach(async(() => { 14 | modalSpy = jasmine.createSpyObj('Modal', ['dismiss']); 15 | modalCtrlSpy = jasmine.createSpyObj('ModalController', ['create']); 16 | modalCtrlSpy.create.and.callFake(function () { 17 | return modalSpy; 18 | }); 19 | 20 | TestBed.configureTestingModule({ 21 | providers: [ 22 | { 23 | provide: ModalController, 24 | useValue: modalCtrlSpy 25 | } 26 | ], 27 | declarations: [ AuthMenuUserComponent ], 28 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 29 | }) 30 | .compileComponents(); 31 | })); 32 | 33 | beforeEach(() => { 34 | fixture = TestBed.createComponent(AuthMenuUserComponent); 35 | component = fixture.componentInstance; 36 | fixture.detectChanges(); 37 | }); 38 | 39 | it('should create', () => { 40 | expect(component).toBeTruthy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-menu-button/auth-menu-user/auth-menu-user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | 4 | @Component({ 5 | selector: 'app-auth-menu-user', 6 | templateUrl: './auth-menu-user.component.html', 7 | styleUrls: ['./auth-menu-user.component.scss'], 8 | }) 9 | export class AuthMenuUserComponent implements OnInit { 10 | 11 | constructor(private modalCtrl: ModalController) { } 12 | 13 | ngOnInit() {} 14 | 15 | dismissModal() { 16 | this.modalCtrl.dismiss(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-register/auth-register.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Name 4 | 5 | 6 | 7 | Email 8 | 9 | 10 | 11 | Password 12 | 13 | 14 | 15 | Confirm Password 16 | 17 | 18 | 22 | Register 23 | {{error}} 24 |
25 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-register/auth-register.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/auth/auth-register/auth-register.component.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth-register/auth-register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Validators, FormBuilder, FormGroup, FormControl } from '@angular/forms'; 3 | import { AuthService } from '../services/auth.service'; 4 | import { User } from '../models/user.model'; 5 | import { ModalController } from '@ionic/angular'; 6 | 7 | @Component({ 8 | selector: 'app-auth-register', 9 | templateUrl: './auth-register.component.html', 10 | styleUrls: ['./auth-register.component.scss'], 11 | }) 12 | export class AuthRegisterComponent implements OnInit { 13 | 14 | registerForm: FormGroup; 15 | error: string; 16 | 17 | constructor( 18 | private formBuilder: FormBuilder, 19 | private auth: AuthService, 20 | private modal: ModalController 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.registerForm = this.formBuilder.group({ 25 | password_confirm: new FormControl('', Validators.required), 26 | password: new FormControl('', Validators.required), 27 | email: new FormControl('', Validators.compose([ 28 | Validators.required, 29 | Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$') 30 | ])), 31 | name: new FormControl('', Validators.compose([ 32 | Validators.required, 33 | Validators.pattern('^[a-zA-Z0-9_.+-]+$') 34 | ])) 35 | }, { validators: this.passwordsMatch }); 36 | } 37 | 38 | onSubmit($event) { 39 | $event.preventDefault(); 40 | 41 | if (!this.registerForm.valid) { return; } 42 | 43 | const newuser: User = { 44 | email: this.registerForm.controls.email.value, 45 | name: this.registerForm.controls.name.value 46 | }; 47 | 48 | this.auth.register(newuser, this.registerForm.controls.password.value) 49 | .then((user) => { 50 | this.modal.dismiss(); 51 | }) 52 | .catch((e) => { 53 | this.error = e.statusText; 54 | throw e; 55 | }); 56 | } 57 | 58 | passwordsMatch(group: FormGroup) { 59 | return group.controls.password.value === group.controls.password_confirm.value ? null : { passwordsMisMatch: true }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { AuthMenuButtonComponent } from './auth-menu-button/auth-menu-button.component'; 7 | import { AuthLoginComponent } from './auth-login/auth-login.component'; 8 | import { AuthRegisterComponent } from './auth-register/auth-register.component'; 9 | import { AuthMenuUserComponent } from './auth-menu-button/auth-menu-user/auth-menu-user.component'; 10 | 11 | import { ApiModule } from '../api/api.module'; 12 | 13 | const entryComponents = [AuthMenuUserComponent, AuthMenuButtonComponent, AuthLoginComponent, AuthRegisterComponent]; 14 | const components = [...entryComponents]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | IonicModule, 21 | ReactiveFormsModule, 22 | ApiModule 23 | ], 24 | entryComponents: entryComponents, 25 | declarations: components, 26 | exports: components, 27 | providers: [] 28 | }) 29 | export class AuthModule {} 30 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | email: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/services/auth.guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot, UrlTree } from '@angular/router'; 3 | import { AuthService } from './auth.service'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthGuardService implements CanActivate { 10 | 11 | constructor( 12 | private auth: AuthService, 13 | private router: Router 14 | ) {} 15 | 16 | canActivate(route: ActivatedRouteSnapshot, 17 | state: RouterStateSnapshot): boolean 18 | | UrlTree 19 | | Observable 21 | | Promise { 22 | if (!this.auth.currentUser$.value) { 23 | this.router.navigateByUrl('/login'); 24 | } 25 | 26 | return this.auth.currentUser$.value !== null; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | import { User } from '../models/user.model'; 4 | import { ApiService } from 'src/app/api/api.service'; 5 | import { catchError, tap } from 'rxjs/operators'; 6 | 7 | const JWT_LOCALSTORE_KEY = 'jwt'; 8 | const USER_LOCALSTORE_KEY = 'user'; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class AuthService { 14 | currentUser$: BehaviorSubject = new BehaviorSubject(null); 15 | constructor( private api: ApiService ) { 16 | this.initToken(); 17 | } 18 | 19 | initToken() { 20 | const token = localStorage.getItem(JWT_LOCALSTORE_KEY); 21 | const user = JSON.parse(localStorage.getItem(USER_LOCALSTORE_KEY)); 22 | if (token && user) { 23 | this.setTokenAndUser(token, user); 24 | } 25 | } 26 | 27 | setTokenAndUser(token: string, user: User) { 28 | localStorage.setItem(JWT_LOCALSTORE_KEY, token); 29 | localStorage.setItem(USER_LOCALSTORE_KEY, JSON.stringify(user)); 30 | this.api.setAuthToken(token); 31 | this.currentUser$.next(user); 32 | } 33 | 34 | async login(email: string, password: string): Promise { 35 | return this.api.post('/users/auth/login', 36 | {email: email, password: password}) 37 | .then((res) => { 38 | this.setTokenAndUser(res.token, res.user); 39 | return res; 40 | }) 41 | .catch((e) => { throw e; }); 42 | // return user !== undefined; 43 | } 44 | 45 | logout(): boolean { 46 | this.setTokenAndUser(null, null); 47 | return true; 48 | } 49 | 50 | register(user: User, password: string): Promise { 51 | return this.api.post('/users/auth/', 52 | {email: user.email, password: password}) 53 | .then((res) => { 54 | this.setTokenAndUser(res.token, res.user); 55 | return res; 56 | }) 57 | .catch((e) => { throw e; }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-item/feed-item.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

{{feedItem.caption}}

5 |
6 |
-------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-item/feed-item.component.scss: -------------------------------------------------------------------------------- 1 | .photo-card{ 2 | max-width: 500px; 3 | overflow: hidden; 4 | background: var(--ion-color-primary-contrast); 5 | margin: 30px 0px; 6 | } 7 | 8 | .photo-card ion-img { 9 | max-height: 532px; 10 | overflow: hidden; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-item/feed-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { FeedItemComponent } from './feed-item.component'; 5 | import { feedItemMocks } from '../models/feed-item.model'; 6 | 7 | describe('FeedItemComponent', () => { 8 | let component: FeedItemComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ FeedItemComponent ], 14 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(FeedItemComponent); 21 | component = fixture.componentInstance; 22 | component.feedItem = feedItemMocks[0]; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | 30 | it('should set the image url to the feedItem', () => { 31 | const app = fixture.nativeElement; 32 | const img = app.querySelectorAll('ion-img'); 33 | expect(img.length).toEqual(1); 34 | expect(img[0].src).toEqual(feedItemMocks[0].url); 35 | }); 36 | 37 | it('should display the caption', () => { 38 | const app = fixture.nativeElement; 39 | const paragraphs = app.querySelectorAll('p'); 40 | expect(([].slice.call(paragraphs)).map((x) => x.innerText)).toContain(feedItemMocks[0].caption); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-item/feed-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; 2 | import { FeedItem } from '../models/feed-item.model'; 3 | 4 | @Component({ 5 | selector: 'app-feed-item', 6 | templateUrl: './feed-item.component.html', 7 | styleUrls: ['./feed-item.component.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | export class FeedItemComponent implements OnInit { 11 | @Input() feedItem: FeedItem; 12 | 13 | constructor() { } 14 | 15 | ngOnInit() {} 16 | } 17 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-list/feed-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
-------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-list/feed-list.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .feed { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | background: var(--ion-color-light-tint); 7 | } -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-list/feed-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnDestroy } from '@angular/core'; 2 | import { FeedItem } from '../models/feed-item.model'; 3 | import { FeedProviderService } from '../services/feed.provider.service'; 4 | import { Subscription } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'app-feed-list', 8 | templateUrl: './feed-list.component.html', 9 | styleUrls: ['./feed-list.component.scss'], 10 | }) 11 | export class FeedListComponent implements OnInit, OnDestroy { 12 | @Input() feedItems: FeedItem[]; 13 | subscriptions: Subscription[] = []; 14 | constructor( private feed: FeedProviderService ) { } 15 | 16 | async ngOnInit() { 17 | this.subscriptions.push( 18 | this.feed.currentFeed$.subscribe((items) => { 19 | this.feedItems = items; 20 | })); 21 | 22 | await this.feed.getFeed(); 23 | } 24 | 25 | ngOnDestroy(): void { 26 | for (const subscription of this.subscriptions) { 27 | subscription.unsubscribe(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload-button/feed-upload-button.component.html: -------------------------------------------------------------------------------- 1 | 6 | Create a New Post 7 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload-button/feed-upload-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload-button/feed-upload-button.component.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload-button/feed-upload-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | import { FeedUploadComponent } from '../feed-upload.component'; 4 | import { AuthService } from 'src/app/auth/services/auth.service'; 5 | import { Subscription } from 'rxjs'; 6 | 7 | @Component({ 8 | selector: 'app-feed-upload-button', 9 | templateUrl: './feed-upload-button.component.html', 10 | styleUrls: ['./feed-upload-button.component.scss'], 11 | }) 12 | export class FeedUploadButtonComponent implements OnInit, OnDestroy { 13 | 14 | isLoggedIn: boolean; 15 | loginSub: Subscription; 16 | 17 | constructor(private modalController: ModalController, private auth: AuthService) { } 18 | 19 | ngOnInit() { 20 | this.auth.currentUser$.subscribe((user) => { 21 | this.isLoggedIn = user !== null; 22 | }); 23 | } 24 | 25 | ngOnDestroy(): void { 26 | if (this.loginSub) { 27 | this.loginSub.unsubscribe(); 28 | } 29 | } 30 | 31 | async presentUploadForm(ev: any) { 32 | const modal = await this.modalController.create({ 33 | component: FeedUploadComponent, 34 | }); 35 | return await modal.present(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload.component.html: -------------------------------------------------------------------------------- 1 |
` 2 | 9 | 10 | Caption 11 | 12 | 13 | 17 | Post 18 | 19 |
20 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload.component.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed-upload/feed-upload.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Validators, FormBuilder, FormGroup, FormControl } from '@angular/forms'; 4 | import { FeedProviderService } from '../services/feed.provider.service'; 5 | 6 | import { LoadingController, ModalController } from '@ionic/angular'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-feed-upload', 11 | templateUrl: './feed-upload.component.html', 12 | styleUrls: ['./feed-upload.component.scss'], 13 | }) 14 | export class FeedUploadComponent implements OnInit { 15 | previewDataUrl; 16 | file: File; 17 | uploadForm: FormGroup; 18 | 19 | constructor( 20 | private feed: FeedProviderService, 21 | private formBuilder: FormBuilder, 22 | private loadingController: LoadingController, 23 | private modalController: ModalController 24 | ) { } 25 | 26 | ngOnInit() { 27 | this.uploadForm = this.formBuilder.group({ 28 | caption: new FormControl('', Validators.required) 29 | }); 30 | } 31 | 32 | setPreviewDataUrl(file: Blob) { 33 | const reader = new FileReader(); 34 | reader.onloadend = () => { 35 | this.previewDataUrl = reader.result; 36 | }; 37 | 38 | reader.readAsDataURL(file); 39 | } 40 | 41 | selectImage(event) { 42 | const file = event.srcElement.files; 43 | 44 | if (!file) { 45 | return; 46 | } 47 | this.file = file[0]; 48 | this.setPreviewDataUrl(this.file); 49 | 50 | } 51 | 52 | onSubmit($event) { 53 | $event.preventDefault(); 54 | this.loadingController.create(); 55 | 56 | if (!this.uploadForm.valid || !this.file) { return; } 57 | this.feed.uploadFeedItem(this.uploadForm.controls.caption.value, this.file) 58 | .then((result) => { 59 | this.modalController.dismiss(); 60 | this.loadingController.dismiss(); 61 | }); 62 | } 63 | 64 | cancel() { 65 | this.modalController.dismiss(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/feed.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { FeedListComponent } from './feed-list/feed-list.component'; 7 | import { FeedItemComponent } from './feed-item/feed-item.component'; 8 | import { FeedUploadComponent } from './feed-upload/feed-upload.component'; 9 | import { FeedUploadButtonComponent } from './feed-upload/feed-upload-button/feed-upload-button.component'; 10 | 11 | import { FeedProviderService } from './services/feed.provider.service'; 12 | 13 | const entryComponents = [FeedUploadComponent]; 14 | const components = [FeedListComponent, FeedItemComponent, FeedUploadComponent, FeedUploadButtonComponent]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | IonicModule, 21 | ReactiveFormsModule 22 | ], 23 | declarations: components, 24 | exports: components, 25 | entryComponents: entryComponents, 26 | providers: [FeedProviderService] 27 | }) 28 | export class FeedModule {} 29 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/models/feed-item.model.ts: -------------------------------------------------------------------------------- 1 | export interface FeedItem { 2 | id: number; 3 | url: string; 4 | caption: string; 5 | } 6 | 7 | export const feedItemMocks: FeedItem[] = [ 8 | { 9 | id: 0, 10 | url: '/assets/mock/xander0.jpg', 11 | caption: 'Such a cute pup' 12 | }, 13 | { 14 | id: 0, 15 | url: '/assets/mock/xander1.jpg', 16 | caption: 'Who\'s a good boy?' 17 | }, 18 | { 19 | id: 0, 20 | url: '/assets/mock/xander2.jpg', 21 | caption: 'Majestic.' 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/feed/services/feed.provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FeedItem, feedItemMocks } from '../models/feed-item.model'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | import { ApiService } from '../../api/api.service'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class FeedProviderService { 11 | currentFeed$: BehaviorSubject = new BehaviorSubject([]); 12 | 13 | constructor(private api: ApiService) { } 14 | 15 | async getFeed(): Promise> { 16 | const req = await this.api.get('/feed'); 17 | const items = req.rows; 18 | this.currentFeed$.next(items); 19 | return Promise.resolve(this.currentFeed$); 20 | } 21 | 22 | async uploadFeedItem(caption: string, file: File): Promise { 23 | const res = await this.api.upload('/feed', file, {caption: caption, url: file.name}); 24 | const feed = [res, ...this.currentFeed$.value]; 25 | this.currentFeed$.next(feed); 26 | return res; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | import { RouterModule } from '@angular/router'; 6 | import { FeedModule } from '../feed/feed.module'; 7 | import { HomePage } from './home.page'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | FeedModule, 12 | CommonModule, 13 | FormsModule, 14 | IonicModule, 15 | RouterModule.forChild([ 16 | { 17 | path: '', 18 | component: HomePage 19 | } 20 | ]) 21 | ], 22 | declarations: [HomePage] 23 | }) 24 | export class HomePageModule {} 25 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/home/home.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/home/home.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/app/home/home.page.scss -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/home/home.page.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { HomePage } from './home.page'; 5 | 6 | describe('HomePage', () => { 7 | let component: HomePage; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ HomePage ], 13 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HomePage); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/home/home.page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | templateUrl: 'home.page.html', 7 | styleUrls: ['home.page.scss'], 8 | }) 9 | export class HomePage { 10 | appName = environment.appName; 11 | } 12 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/menubar/menubar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ appName }} 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/menubar/menubar.component.scss: -------------------------------------------------------------------------------- 1 | ion-title { 2 | font-weight: bold; 3 | font-family: 'Dancing Script', cursive; 4 | font-size: 180%; 5 | } -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/menubar/menubar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { MenubarComponent } from './menubar.component'; 5 | import { environment } from '../../environments/environment'; 6 | 7 | 8 | describe('MenubarPage', () => { 9 | let component: MenubarComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ MenubarComponent ], 15 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(MenubarComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | 30 | it('title should be enviornment.AppTitle', () => { 31 | const app = fixture.nativeElement; 32 | const title = app.querySelectorAll('ion-title'); 33 | expect(title.length).toEqual(1); 34 | expect(title[0].innerText).toEqual(environment.appName); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/app/menubar/menubar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | 4 | @Component({ 5 | selector: 'app-menubar', 6 | templateUrl: './menubar.component.html', 7 | styleUrls: ['./menubar.component.scss'], 8 | }) 9 | export class MenubarComponent implements OnInit { 10 | public appName = environment.appName; 11 | 12 | constructor() { } 13 | 14 | ngOnInit() {} 15 | 16 | } 17 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/nd0067-c4-deployment-process-project-starter/c7612a4e5acdceee25850f014dd244c6f6f4bd3b/udagram/udagram-frontend/src/assets/icon/favicon.png -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: true, 7 | appName: "Udagram", 8 | apiHost: "http://localhost:8080/api/v0", 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | appName: 'Udagram', 8 | apiHost: 'http://localhost:8080/api/v0' 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/global.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/theming/ 2 | @import '~@ionic/angular/css/core.css'; 3 | @import '~@ionic/angular/css/normalize.css'; 4 | @import '~@ionic/angular/css/structure.css'; 5 | @import '~@ionic/angular/css/typography.css'; 6 | @import '~@ionic/angular/css/display.css'; 7 | @import '~@ionic/angular/css/padding.css'; 8 | @import '~@ionic/angular/css/float-elements.css'; 9 | @import '~@ionic/angular/css/text-alignment.css'; 10 | @import '~@ionic/angular/css/text-transformation.css'; 11 | @import '~@ionic/angular/css/flex-utils.css'; 12 | 13 | // Fancy Fonts 14 | @import url('https://fonts.googleapis.com/css?family=Dancing+Script:700'); -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/theming/ 3 | 4 | /** Ionic CSS Variables **/ 5 | :root { 6 | /** primary **/ 7 | --ion-color-primary: #3880ff; 8 | --ion-color-primary-rgb: 56, 128, 255; 9 | --ion-color-primary-contrast: #ffffff; 10 | --ion-color-primary-contrast-rgb: 255, 255, 255; 11 | --ion-color-primary-shade: #3171e0; 12 | --ion-color-primary-tint: #4c8dff; 13 | 14 | /** secondary **/ 15 | --ion-color-secondary: #0cd1e8; 16 | --ion-color-secondary-rgb: 12, 209, 232; 17 | --ion-color-secondary-contrast: #ffffff; 18 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 19 | --ion-color-secondary-shade: #0bb8cc; 20 | --ion-color-secondary-tint: #24d6ea; 21 | 22 | /** tertiary **/ 23 | --ion-color-tertiary: #7044ff; 24 | --ion-color-tertiary-rgb: 112, 68, 255; 25 | --ion-color-tertiary-contrast: #ffffff; 26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 27 | --ion-color-tertiary-shade: #633ce0; 28 | --ion-color-tertiary-tint: #7e57ff; 29 | 30 | /** success **/ 31 | --ion-color-success: #10dc60; 32 | --ion-color-success-rgb: 16, 220, 96; 33 | --ion-color-success-contrast: #ffffff; 34 | --ion-color-success-contrast-rgb: 255, 255, 255; 35 | --ion-color-success-shade: #0ec254; 36 | --ion-color-success-tint: #28e070; 37 | 38 | /** warning **/ 39 | --ion-color-warning: #ffce00; 40 | --ion-color-warning-rgb: 255, 206, 0; 41 | --ion-color-warning-contrast: #ffffff; 42 | --ion-color-warning-contrast-rgb: 255, 255, 255; 43 | --ion-color-warning-shade: #e0b500; 44 | --ion-color-warning-tint: #ffd31a; 45 | 46 | /** danger **/ 47 | --ion-color-danger: #f04141; 48 | --ion-color-danger-rgb: 245, 61, 61; 49 | --ion-color-danger-contrast: #ffffff; 50 | --ion-color-danger-contrast-rgb: 255, 255, 255; 51 | --ion-color-danger-shade: #d33939; 52 | --ion-color-danger-tint: #f25454; 53 | 54 | /** dark **/ 55 | --ion-color-dark: #222428; 56 | --ion-color-dark-rgb: 34, 34, 34; 57 | --ion-color-dark-contrast: #ffffff; 58 | --ion-color-dark-contrast-rgb: 255, 255, 255; 59 | --ion-color-dark-shade: #1e2023; 60 | --ion-color-dark-tint: #383a3e; 61 | 62 | /** medium **/ 63 | --ion-color-medium: #989aa2; 64 | --ion-color-medium-rgb: 152, 154, 162; 65 | --ion-color-medium-contrast: #ffffff; 66 | --ion-color-medium-contrast-rgb: 255, 255, 255; 67 | --ion-color-medium-shade: #86888f; 68 | --ion-color-medium-tint: #a2a4ab; 69 | 70 | /** light **/ 71 | --ion-color-light: #f4f5f8; 72 | --ion-color-light-rgb: 244, 244, 244; 73 | --ion-color-light-contrast: #000000; 74 | --ion-color-light-contrast-rgb: 0, 0, 0; 75 | --ion-color-light-shade: #d7d8da; 76 | --ion-color-light-tint: #FAFAFA; 77 | } 78 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "skipLibCheck": true 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "typings" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /udagram/udagram-frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | --------------------------------------------------------------------------------