Skip to content

Collaborative Online Edition System | Angular2+ & Node.js |

License

Notifications You must be signed in to change notification settings

jonnyrocks/COJ_project

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

COJ_project

How build the both client and server sides for this App

╔══════════════════════╗        ╔═══════════╗ 
║ App.component.html.ts║-------➡║ index.html║-------
╚══════════════════════╝        ╚═══════════╝       |
    ↗         ↖                                     |
╔════════╗   ╔════════╗                             |
║ Navbar ║   ║ Router ║                             |
╚════════╝   ╚════════╝                             |
                  ↗    ↖                            |
╔══════════════════════╗ ╔═══════════════════════╗  |
║ ProblemListCompinent ║ ║ ProblemDetailComponent║  |
╚══════════════════════╝ ╚═══════════════════════╝  |
        ↗                ↘      ↓ onInit function   |
╔══════════════════════╗   ╔════════════╗           |
║  NewProblemCompinent ║ ➡ ║ DataService║           |
╚══════════════════════╝   ╚════════════╝           |
              (api Request)  ↑  ↓           ╔═══════════╗
-----------------------------↑  ↓ --------  ║public/    ║
                             ↑    ↘         ║ index.html║
                             ↑       ↘      ╚═══════════╝
╔══════════════╗     ╔═══════════╗    ↘    ↗  Index   
║ProblemService║ ↔↔↔ ║Rest Router║    ↓    ↑    Router
╚══════════════╝     ║  rest.js  ║   ╔═══════════╗
    ↓↑               ╚═══════════╝ ↖ ║ Server.js ║
    ↓↑                               ╚═══════════╝
    ↓↑                                     ║
╔══════════════╗      ╔══════════╗ connect ║ 
║ ProblemModel ║  ←←← ║ MongoDB  ║ ════════╝ 
╚══════════════╝      ╚══════════╝

How a request was sent from frontend to backend and back to browser

  • DataService(FrontEnd) call a Http Request
  • for example getProblems()
  • api Request send to server.js
  • Server.js send request to RestRouter to find a right ProblemService
  • Problem help to get data from ProblemSchema
  • ProblemModel search the data from Database and send back to RestRouter
  • RestRouter get the problems data and chagne it to a JSON file
  • Send it back to DataService
  • Since problem-list have subscripted, the chagne will call to frontend angular cli
  • By data binding with html, frontend contents change

Week1


Document Introduction

e2e / karma.conf.js / protractor.cong.js

For testing

node_modules

Made by npm, then, npm will install libraries by package.json file

gitignore

Marked documents don't need to be uploaed eg. dependencies/node_module

.angular-cli.json

  • apps -> root -> src : the startpoint of our app
  • outDir -> "dist" : files need to be uploaded (we'll change in the future)
  • style -> style.css : global stylesheets (eg. bootstrap)

src

Template(VIEW)

Composing HTML templates with Angularized markup 

Component(Model)

Classes to manage or support the templates.
Interacting with the VIEW through an API of properites and methods. 
  • selector : How to show the page in index.html
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

Service

Adding application logic. Almost anything can be a service. (Logic Service / Data Service etc.)

Module

  • declarations : Component
  • imports : Module
  • providers : Service
  • bootstrap : Enter
Boxing components and services. (收納箱)
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Components need to be Created

Client (Data will connect with a fake)

╔  Router: app.routes.ts 
║      ↓
║      ↓       ╔ Problem-list
╟ Conponents ══╟ Problem-detail
║              ╟ New Problem
║              ╚ Navbar
║                   ↓ (without Navbar)
╚  Data Service ←  ↙          
  • Made a dir. components in src/app/
  • Used ag-cli automatically create four files
  • and add ProblemListComponent in app.module.ts
$mkdir components
$ng g c problem-list

Add Bootstrap

  • install bootstrap & jQuery package from npm
  • Since we don't use all bootstrap module, suggest to use from npm
npm install bootstrap --save
npm install jquery --save
  • Add both jQuery nd Bootstrap in the script and stylesheet from ".angular-cli.json"
      "styles": [
        "styles.css",
        "../node_modules/bootstrap/dist/css/bootstrap.min.css"
      ],
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/bootstrap/dist/js/bootstrap.js"
      ],

Problem List Component

In problem-list.component.ts

Use tag <app-problem-list> made by Angular in "app.component.html.ts" 
could show the content from "problem-list.component.html"  
index.html(<app-root>) <- 
app.component.html.ts(<app-problem-list>) <-
problem-list.component.html.ts
 selector: 'app-problem-list',
  templateUrl: './problem-list.component.html',

Add modeles for gaining Problems

  • Opne a model file under the app
$ mkdir models
$ touch models/problem.model.ts
  • Export a problem model
export class Problem{
    id: number;
    name: string;
    desc: string;
    diff: string;
}

Create a mock data

  • In problem-list component, we need to import the model and create a variable with mock datas
const PROBLEMS: Problem[] = [
  {
    id: 1,
    name: "Two Sum",
    desc: `Given an array of integers...`,
    diff: "easy"
  },
  /// ...
];

Model

  • In model, we will provide problems to VIEW which only can access content inside ProblemListComponent.

  • ngOnInit() : initization

  • getProblems(): give model PROBLEMS outside to the problems inside Component

  • :void : give the "type" of callback value to the method.

export class ProblemListComponent implements OnInit {
  problems = []; // give a list
  
  constructor() { }

  ngOnInit() {
    this.getProblems();
  }

  getProblems(): void{
    this.problems = PROBLEMS;
  }

}

View

(problem-list.component.html)

  • Build up a Structure with bootstrap
  • "*ngFor": looping data from database and print it out
  • Let each value in problems array save in a variable problem
*ngFor="let problem of problems"
  • Gain the data from problems model and show on HTML markup (data binding)
{{problem.diff}}
  • Also need a binding for class in span element, since we need to add-in a styling tag based on difficulty.
<span class="{{'pull-left label difficulty diff-' + problem.diff.toLowerCase()}}">

Add in CSS Stylesheet

  • problem-list.component.css
.difficulty {
  min-width: 65px;
  margin-right: 10px;
}
....

Seperate the Data and Model/View

  • Create a single reusable data service and inject it into the components that need it.

Move mock data to seperate file

  • to mock-problems.ts and export it
import { Problem } from "./models/problem.model";
export const PROBLEMS: Problem[] = [
 //....
]

Seperate the getting problem logic

$cd src/app
$mkdir services
$cd services
$ng g s data

Provide the servie in app.module

@NgModule({
  providers: [
    DataService
  ],
})

Add service in DataService

  • Import problem model and mock problems
import { Injectable } from '@angular/core';
import { Problem } from '../models/problem.model';
import { PROBLEMS } from '../mock-problems';
  • Add Method to help get singal problem with id and whole problems
  getProblems():Problem[]{
    return PROBLEMS;
  }

  getProblem(id: number): Problem{
    return PROBLEMS.find((problem) => problem.id === id );
  }

Inject the service into problem-list component

import {DataService} from '../../services/data.service';

    problems: Problem[];
    constructor(private dataService: DataService) { }

    getProblems(): void{
    this.problems = this.dataService.getProblems();

Problem Detail Component

ng g c problem-detail

Single page app Routing

  • Client side routing is the same as server side routing,
  • but it's ran in the browser

Add app.routes.ts

touch app/app.routes.ts
  • Import angular/router && Components
import { Routes, RouterModule } from '@angular/router';
import { ProblemListComponent } from './components/problem-list/problem-list.component';
import { ProblemDetailComponent } from './components/problem-detail/problem-detail.component';

Settle a router and export for Root

const router: Routes =[
    {
        path: "",
        redirectTo: 'problems',
        pathMatch: 'full'
    },
    {
        path: 'problems',
        component: ProblemListComponent
    },
    {
        path: 'problems/:id',
        component: ProblemDetailComponent
    },
    {
        path: '**',
        redirectTo: 'problems'
    }

];

export const routing = RouterModule.forRoot(router);

Import in app.module

  imports: [
    BrowserModule,
    routing
  ],

Change app.c.html for showing router page

<router-outlet></router-outlet>

add a router link in an anchor tag (problem-list.component.html)

 <a class="list-group-item" *ngFor="let problem of problems"
      [routerLink]=['/problems',problem.id]>

Add Pipe for SUMMARY

  • Add summary.pipe.ts
$ng -g -p summary
  • Import Pipe, PipeTransform and Give a logic to SUMMARY
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
    name: "summary"
})

export class SummaryPipe implements PipeTransform {
    transform(value: string, limit?: number){
        if (!value)
            return null;
        return value.substr(0,70) + '...';
    }
}
  • Add SummaryPipe into Module
@NgModule({
  declarations: [
    AppComponent,
    ProblemListComponent,
    ProblemDetailComponent,
    SummaryPipe
  ],
  • Used Summary in html markup
<div class="list-group-item description "> {{problem.desc | summary}}</div>

Problem Detail Component

  • Import Probelm from model
import { Problem } from '../../models/problem.model';

export class ProblemDetailComponent implements OnInit {
  problem : Problem[];
}
  • Import Data from DataService and Gain data from ActivedRoute
import { DataService } from '../../services/data.service';
import { ActivatedRoute, Params} from '@angular/router';

  constructor(private dataService: DataService, private route: ActivatedRoute)  { }
  • Used ngOnInit to get the id from Route and change the parameter from String into Number
  ngOnInit() {
    this.route.params.subscribe(params => {
      this.problem = this.dataService.getProblem(+params['id']); // Change String into Number
    })
  }
  • Add HTML markup (*ngIf to show existed problem)
<div class="container" *ngIf = "problem">

NEW-Problem Component

  • Add new-problem component
$ ng g c new-problem
  • Import Form module
  imports: [
    BrowserModule,
    routing,
    FormsModule
  ],
  • HTML markup for New Problem Form
  • [()]: "Banana in the Box" for TWO-WAY data binding
  • []: Property Binding (One-Way)
  • (): Event Binding (One-Way)
  • Input Name +
<div>
  <form #formRef = "ngForm">
    <div class="form-group">
      <label for="problemName">Problem Name</label>
      <input name="problemName" id="problemName" 
      class="form-control" type="text" required 
      placeholder="Please Enter Problem Name" 
      [(ngModel)]="newProblem.name">
    </div>
    <div></div>
    .
    .
    .
  </form>
</div>
  • Select Difficulities <\select> + <\option>
<div class="form-group">
        <label for="problemDiff">Difficulty</label>
        <select name="diff" id="diff" class="form-control"  
        [(ngModel)]="newProblem.diff">
          <option *ngFor = "let diff of diffs" [value] = "diff">
            {{diff}}
          </option>
        </select>
      </div>
  • Type Area <\textarea><\textarea>
      <div class="form-group">
          <label for="problemDesc">Problem Description</label>
          <textarea name="problemDesc" id="problemDesc" 
          class="form-control" required 
          placeholder="Please Enter Problem Description" 
          [(ngModel)]="newProblem.desc" rows="3">
          </textarea>
      </div>
  • Submit Button (with Click EVENT)
<div class="row">
    <div class="col-md-12">
     <button type="submit" class="btn btn-info pull-roght"
    (click) = "addProblem()">
        Add Problem
    </button>
    </div>
</div>

Modle and Data in New-Problem

  • Import Problem Model into new-problem.component and Add a string array of difficulities
import { Problem } from '../../models/problem.model';
  • Give a const variable of default Problem
const DEFAULT_PROBLEM: Problem = Object.freeze({
  id:0,
  name:'',
  desc:'',
  diff:'easy'
});
  • Assign Value to Prpblem and then send the data to DataService for processing (by addProblenm())
  • Import DataService
  • Connent to Service in constructor
  • Chagne new Problem into Default Problem after each added
newProblem: Problem = Object.assign({}, DEFAULT_PROBLEM);
  diffs: string[] = ['easy', 'medium', 'hard', 'super'];
  constructor(private dataService: DataService) { }
  addProblem(){
    this.dataService.addProblem(this.newProblem);
    this.newProblem = Object.assign({}, DEFAULT_PROBLEM);
  }
  • dataService modify: Since we couldn't change the array of PROBLEMS, we saved it to another variable for adding new Problems
problems: Problem[] = PROBLEMS;
  • Also, change the original PROBLEM to this.problems which we setted last step
  getProblems():Problem[]{
    return this.problems;
  }
  • Add a method of "addProblem()"
  • Give a new problem an id and push this object into problems array
  addProblem(problem: Problem): void{
    problem.id = this.problems.length + 1;
    this.problems.push(problem);
  }

Navbar Component

  • create files for navbar
ng g c navbar
  • Put navbar in app.component.html
<app-navbar></app-navbar>
<router-outlet></router-outlet>
  • Copy navbar codes from bootstrap
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container"> 
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-2" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
......

Add footer

  • install font-awsome
npm install --save font-awesome
  • Add font-awesome stylesheet in ".angular-cli.json"
"../node_modules/font-awesome/css/font-awesome.css"
  • Add html
<div class="container">
  <div class="social-icons">
   <ul class="list-inline">
     <li><a href="mailto:[email protected]?Subject=Visiter from your website" target="_blank"  ><i class="fa fa-envelope"></i></a></li>
     <li><a href="https://github.com/WeiChienHsu" target="_blank"><i class="fa fa-github" ></i></a></li>
     <li><a href="https://www.linkedin.com/in/weichien-hsu/" target="_blank"><i class="fa fa-linkedin"></i></a></li>
   </ul>
 </div> <!-- /.social-icons -->
</div>  


Week 2

Copy Week1 to Week2 (for homework)

cp -R week1 week2

Initialize oj-server

mkdir oj-server
  • Add scripts > "start": "node server.js"
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  • initize npm
npm init
  • Add .gitignore
touch .gitignore
# dependencies
/node_modules
  • install express
npm install express --save

Create a RESTful API Server (Feature)

  • User can GET and POST problems from server using RESTful API

STEP 1

  • Handle server-side routing
  • Create server.js
  • Start project with nodemon

STEP 2

  • GET /api/v1/problems (get all problems)
  • GET /api/v1/problems/:id (get problem by id)
  • POST /api/v1/problems (add a new problem)

STEP 3

  • Add Router to server.js

STEP 4

  • Create problemService to READ/WRITE the problem data (Mock data)

STEP 5

  • Test with POSTMAN

STEP 6

  • Handle GET /api/v1/problems/:id
  • Handle POST /api/vi/problems requests
  • npm install body-parser --save

STEP 7

  • Test with POSTMAN

Set up EXPRESS

  • open server.js file and C&P init document
const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Design API

  • give a ROUTER to deal with specific stuff then we can clearify what the requests are asked for easier
app.use('/api/v1', restRouter);
  • Open a folder "routes" to deal with all routing problem
  • add new file "rest.js" (writting in express)
mkdir routes
touch routes/rest.js

rest.js

const express = require('express');
const router = express.Router();
const problemService = require('../services/problemService');
  • Don't need to write /api/vi since we have alrady set up by useing "app.use" in server.js
  • Add a promise
  • For example, get a request of "getProblem()" and send a problems out
router.get('/problems', (req, res) => {
    problemService.getProblems()
        .then(problems => res.json(problems));
});
  • Export router
module.exports = router;
  • in server.js, import restRouter
const restRouter = require('./routes/rest.js');

Build up a problemService

getPorblems()

  • To deal with data, we need a problemService to connect with our router
mkdir services
touch services/problemService.js
  • Add a mock data since we haven't touch database
problems = [
  ....
]
  • Give a function to get Problems and export it.
const getProblems = function(){
    console.log('In the problem service get problems')
    return new Promise((resolve, reject) => {
        resolve(problems);
    })
}

const getProblem = function(){}

module.exports = {
    getProblems,
    getProblem
}

getPorblem()

rest.js

  • In rest.js, given a request parameter(id) to getProblem() in problemService to catch the problem we needed
  • give a + to change id from string into number
router.get('/problems/:id', (req, res) =>{
    const id = req.params.id;
    problemService.getProblem(+id)
        .then(problem => res.json(problem))
} )

problemService.js

  • In problemService.js
const getProblem = function(id){
    console.log("In the problem service get single problem");
    return new Promise((resolve, reject) => {
        resolve(problems.find(problem => problem.id === id));
    });
}

PostPorblems()

  • Body Parser (jsonparser) help to take JSON object from body for sending POST request.
  • In rest.js
npm install body-parser --save
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();
  • Post Problem
  • Need a jsonParser as a middleware to transfer "req.body" into JSON object
router.post('/problems',jsonParser, (req, res) => {
    problemService.addProblem(req.body)
        .then(problem => { //resolve
            res.json(problem);
        },(error) => { //reject
            res.status(400).send('Problem name already exists!');
        })
})
  • Add addProblem function in problemService
const addProblem = function(newProblem){
    return new Promise((resolve, reject) => {
        if (problems.find(problem => problem.name === newProblem.name)){
            reject('Problem already exists!');
        } else {
            newProblem.id = problems.length + 1;
            problems.push(newProblem);
            resolve(newProblem);
        }
    });
}

Testing by POSTMAN

  • GET problem by typing in address and it'll send out out mock data in JSON
  • Reject : frontend will have a handler such as the same JS promise
"http://localhost:3000/api/v1/problems"
  • POST problem will be send as a JOSN file in body and then we need to use body-parser to read the content in backend
  • Send a JOSN object POST request by Postman
  • Same name sent, should respone "Problem name already exists!"
{
	    "name": "3Sum",
        "desc": "Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.",
        "difficulty": "medium"
}
  • Post a right request will respone right messages with new id
{
    "name": "31Sum",
    "desc": "Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.",
    "difficulty": "medium",
    "id": 6
}

Integrate MongoDB

Step 1

  • Register an account on mLab and create a database

Step 2

  • Install mongoose and connect to MongoDB on mLab
npm install mongoose --save

Step 3

  • Add schema problemModel

Step 4

  • refactor problemService to READ/WRITE data FROM/TO MongoDB

Mongoose Connecting

  • Reqire and connect mongoose
  • server.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://user:[email protected]:23976/cs503-1705test');

Build up a Schema for frontend to read

mkdir models
touch models/problemModel.js
  • problemModel.js
const mongoose = require('mongoose');
const ProblemSchema = mongoose.Schema({
    id: Number,
    name: String,
    desc: String,
    diff: String
});

const ProblemModel = mongoose.model('ProblemModel', ProblemSchema);
module.exports = ProblemModel;

Use data from MongoDB

  • In problemService, we need to get problem from database

  • No longer need the old getProblem function since it gets problem from our mock data.

  • Directly get problems from database

  • ProblemModel.find(condition, callback[err, data]) : no condition and callback first deal with error and reject or resolve(send back) the data

getProblems

  • findOne({id: neameYouInput}, (err, problem))
const getProblem = function(id){
    return new Promise((resolve, reject) => {
        ProblemModel.findOne({id: id}, (err, problem) => {
            if (err) {
                console.log("In the problem service get problem");
                reject(err);
            } else {
                resolve(problem);
            }
        });
    });
}

getProblem

const getProblem = function(id){
    return new Promise((resolve, reject) => {
        ProblemModel.findOne({id: id}, (err, problem) => {
            if (err) {
                console.log("In the problem service get problem");
                reject(err);
            } else {
                resolve(problem);
            }
        });
    });
}

addProblem

  • If found a same id which means the data exist, we need to reject
  • Count the problems and assign a new id
  • Create a mongoProblem for sending data to MongoDB (use mongoProblem.save())
const addProblem = function(newProblem){
    return new Promise((resolve, reject) => {
        ProblemModel.findOne({name: newProblem.name}, (err, data) => {
            if (data) { 
                // find a same id
                reject('Problem already exists!');
            } else {
                ProblemModel.count({}, (err, count) => {
                    newProblem.id = count + 1;
                    //Save into MongoDB
                    const mongoProblem = new ProblemModel(newProblem);
                    mongoProblem.save();
                    resolve(mongoProblem);
                });
            }
        });
    });
}

Connect

STEP 1

  • Refactor client-side data.service to async
  • in app.module.ts
  • import HttpClientModule

STEP 2

  • Refactor all components calling data.service
  • Problem-list.component.ts
  • Problem-detail.component.ts
  • New-Problem.component.ts

STEP 3

  • Update .angualr-cli.json, change location of ourDir
  • In the future, you can use ng build -- watch in/ oj-client, we are not using localhost:4200 anymore

STEP 4

  • Send static web pages from server to browser

STEP 5

  • Solve "refresh" issue

Refactor client-side data.service

  • Call out DataService which used to connect with mock data
  • Http Module (in app.module)
import { HttpClientModule } from '@angular/common/http';

  imports: [
    BrowserModule,
    routing,
    FormsModule,
    HttpClientModule
  ],
  • Import HttpClient, HttpHeaders, HttpResponse:

  • Observable: Observe Data Flow. Non-stop sending data, with Values, Complete, Error.

  • BehaviorSubject: Always exist.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/toPromise';
  • No longer need the mock problem and change it to the problemSource putting all problems inside and marks it as private
 // problems: Problem[] = PROBLEMS;

  private _problemSource = new BehaviorSubject<Problem[]>([]);
  • Register Angular HttpClient
 constructor(private HttpClient: HttpClient) { }

getProblems

  • Call Api v1, Endpoint ('api/v1/problem')
  • Transfer BehaviorSubject into Promise
  • "Next" : receive the changes and check the latest data from database
  • "Catch" : for frontend to handle Error, if there's error, use handleError method
  getProblems():Observable<Problem[]>{
    this.httpClient.get('api/v1/problems')
      .toPromise()
      .then((res: any) => {
        this._problemSource.next(res);
      })
      .catch(this.handleError);
      return this._problemSource.asObservable();
  }
  • Create a function to handle Error
  private handleError(error: any): Promise<any> {
    return Promise.reject(error.body || error);
  }

getProblem

  getProblem(id: number): Promise<Problem>{
    return this.httpClient.get(`api/v1/problems/${id}`)
      .toPromise()
      .then((res: any) => res)
      .catch(this.handleError);
  }

addProblem

  • Since we need to send a POST request to API, we need to give a hearder first (Content-Type).

  • Post Request will send url+body+header and a callback function

  • Call getProblems: since frontend won't know the change of database when we add a problem so after we update problem list, we need to call back new problem list in frontend

  addProblem(problem: Problem) {
      const options = { headers: new HttpHeaders({'Content-Type': 'application/json' })};
      return this.httpClient.post('api/v1/problems', problem, options)
        .toPromise()
        .then((res: any) => {
          this.getProblems();
          return res;
        })
        .catch(this.handleError);
    }

Change Problem-list.component from sync to async (同步 -> 異步) and add Subscription

  • Import OnDestroy/Subscription
  • subcribe when OiInit, then when the problem data change, our frontend will know
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
  • subscription
subscriptionProblems: Subscription;
  • Oninit
  • OnDestroy
  ngOnInit() {
    this.getProblems();
  }

  ngOnDestroy(){
    this.subscriptionProblems.unsubscribe();
  }
  • change the getProblem from sync to async
  getProblems(): void{
    this.subscriptionProblems = this.dataService.getProblems()
      .subscribe(problems => this.problems = problems);
  }

Problem-detail

  • getProblem callback is a Promise, we need to change how onInit works.
  ngOnInit() {
    this.route.params.subscribe(params => {
      this.dataService.getProblem(+params['id'])
        .then(problem => this.problem = problem)
    });

New Problem

  • Dont need to change, we didn't use the data from database

Run oj-client into Production

  • Set all UI documents will into publuc
  • In .angular-cli.json, change "outDir" to '../public'
  • in Oj-client, and heres a public file
ng build

Add a logic to connect oj-server and index.html

  • In server.js, add another router to deal with pade localhost3000
app.use('/', indexRouter);
  • In index.js, use "Path" from nodeJS to get the content from public when someone send a request asking localhost3000
const express = require('express');
const router = expree.Router();
const path = require('path');

router.get('/', (req, res) => {
    res.sendFile('index.html', {root: path.join(__dirname, '../../public/')});
});

module.exports = router;

Face an Error

  • Fail to load the static files such as JS, CSS, HTML
localhost/:13 GET http://localhost:3000/inline.bundle.js net::ERR_ABORTED
localhost/:13 GET http://localhost:3000/polyfills.bundle.js net::ERR_ABORTED
localhost/:13 GET http://localhost:3000/scripts.bundle.js net::ERR_ABORTED
localhost/:13 GET http://localhost:3000/styles.bundle.js net::ERR_ABORTED
localhost/:13 GET http://localhost:3000/vendor.bundle.js net::ERR_ABORTED
localhost/:13 GET http://localhost:3000/main.bundle.js net::ERR_ABORTED
  • Give a path
const path = require('path');

app.use(express.static(path.join(__dirname, '../public/')));

Frontend Client Routing

  • Server couldn't deal with frontend router
  • After finishing indexRouter and restRouter logic, backend will directly send back the index.html nomether what request fronend (client) send
app.use((req, res) => {
    res.sendFile('index.html', {root: path.join(__dirname, '../public/')})
})


Week 3

  • Create Editor Component
  • Embedding Ace Editor (third party)
  • Add language select, reset and submit buttons
  • Install socket.io on oj-client and oj-server
  • Establish socket connection
  • Synchronize the editor buffer content
  • Store and restore socket sessions with Redis

Reference

Before Editting

-Copy the week2 code

cp -r week2 week3
  • Create Editor Component
ng g c editor

Add Editor

  • In porblem details html
  • Hidden in x samll screen
  <div class="hidden-xs col-md-8">
    <app-editor></app-editor>
  </div>

ACE Editor

  • Install package
npm install --save ace-builds
  • Add JS packages in .angular-cli
  • src-min-noconflict for not conflicting
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/bootstrap/dist/js/bootstrap.js",
        "../node_modules/ace-builds/src-min-noconflict/ace.js",
        "../node_modules/ace-builds/src-min-noconflict/mode-java.js",
        "../node_modules/ace-builds/src-min-noconflict/mode-python.js"
      ],

Editor Styling

  • Declare ace in component.ts
declare const ace: any;
  • Add Script in "ngOnInit()"
  • set a editor value
  • Add an editor variable inside class
  export class EditorComponent implements OnInit {
  editor: any;

  ngOnInit() {
    this.editor = ace.edit("editor");
    this.editor.setTheme("ace/theme/eclipse");
    this.editor.getSession().setMode("ace/mode/java");
    this.editor.setValue(this.defaultContent['Java'])
  }
  • Add Default Content
defaultContent = {
   'Java': `public class Example {
     public static void main(String[] args) {
         // Type your Java code here
     }
   }`,
   'Python': `class Solution:
   def example():
       # Write your Python code here`
  };
  • HTML
<div id="editor"></div>
  • CSS
  • media: screen
@media screen {
    #editor {
        height: 600px;
    }
}
  • Styling
#editor {
      height: 600px;
    }
    .lang-select {
      width: 100px;
      margin-right: 10px;
    }
    header .btn {
      margin: 0 5px;
    }
    footer .btn {
      margin: 0 5px;
    }
    .editor-footer, .editor-header {
      margin: 10px 0;
    }
    .cursor {
      /*position:absolute;*/
      background: rgba(0, 250, 0, 0.5);
      z-index: 40;
      width: 2px !important;
    }

Add DropDown in Editor

  • Choose language
  • setLanguage()
<section>
<header class="editor-header">
    <select class="form-control pull-left lang-select" name="language"
    [(ngModel)]="language" (change)="setLanguage(language)">
        <option *ngFor="let language of languages" [value]="language">
            {{language}}
        </option>
    </select>
  • Reset Button
    <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">
    Reset
    </button>
  • Modal Dalogue
  • With reset editor()
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
        <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Are you sure</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal-body">
            You will lose current code in the editor, are you sure?
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
            <button type="button" class="btn btn-primary" data-dismiss="modal"
            (click)="resetEditor()">Reset</button>
        </div>
        </div>
    </div>
    </div>
</header>
  • Editor inside Row
<div class="row">
    <div id="editor"></div>
</div>
  • Submit button
<footer class="editor-footer">
    <button type="button" class="btn btn-success pull-right" 
    (click)="submit()">Submit Solution</button>
</footer>
</section>

Add methods in editor component

  • Setup languages and default language
  languages: string[] = ['Java', 'Python'];
  language: string = 'Java';
  • resetEditor
  resetEditor(): void {
    this.editor.setValue(this.defaultContent[this.language]);
  }
  • setLanguage
  setLanguage(language: string): void{
    this.language = language;
    this.resetEditor();
  }
  • subimt
  submit(): void{
    const userCode = this.editor.getValue();
    console.log(userCode); // temp
  }

Refactor editor logic

  • call resetEditor when Oninit
  • move the getSession function to reset function and change its theme based on language we chose
  ngOnInit() {
    this.editor = ace.edit("editor");
    this.editor.setTheme("ace/theme/eclipse");
    this.resetEditor();
    this.editor.$blockScrolling = Infinity;
  }

    resetEditor(): void {
    this.editor.setValue(this.defaultContent[this.language]);
    this.editor.getSession().setMode("ace/mode/" + this.language.toLocaleLowerCase());
  }

Synchronize the editor buffer content

Client Socket

  • Install socket.io
npm install --save socket.io

Cleint side will have a collaboration service deals with server

  • Add module in client side and add in app.module
ng g s collaboration
  • App.module
  providers: [
    DataService,
    CollaborationService
  ],
  • .angular-cli
"../node_modules/socket.io-client/dist/socket.io.js"
  • collaboration.service
  • Every times when you connect, socket send a message to window.location.origin (Backend server Endpoint)
init(): void {
    this.collaborationSocket = io(window.location.origin, {query: "message=" + "haha"});
  • Add Event handler to receive message
    this.collaborationSocket.on('message', (message) => {
      console.log('message received from server' + message);
    });
  • Import Collaboration Service in editor component, when editor init, it will call an collaboration.init() function
constructor( private collaboration: CollaborationService) { }

ngOnInit(){
  this.collaboration.init();
}

Server Socker

  • Install socket.io
npm install --save socket.io
  • Open a file for editor Socket Service
touch editorSocketService.js
  • Receive message from client
module.exports = function(io){
    io.on('connection', (socket) => {
        console.log(socket);
        const message = socket.handshake.query['message'];
        console.log(message);
  • Send back message to client
io.to(socket.id).emit('message', 'hehe from server');
  • Build up a Http server in server.js
const http = require('http');
const socketIO = require('socket.io');
const io = socketIO();
const editorSocketService = require('./services/editorSocketService')(io);
  • Open a new line for server
const server = http.createServer(app);
io.attach(server);
server.listen(3000);
server.on('listening', onListening);

function onListening(){
    console.log('App listening on port 3000')
}

After testing, refactor console.log

Get the session_id to server

  • Edit Component get a "session id" from activeRoute (the same way we get that id from problem list to problem detail)

  • Send id to collaboration service to tell where Url the user now located

  • Collaboratoin will send the same information to editorSocketService

Eidtor Component

  • Import
import { ActivatedRoute, Params } from '@angular/router';
export class EditorComponent implements OnInit {
  sessionId: string;

  constructor( private collaboration: CollaborationService,
  private route: ActivatedRoute) { }

  
  • In ngOnInit, get id first by route params senging into "sessionId" and call initEditor (Make those methods a function)

  • Send editor and session id when using clollaboration init

  • collaboration.init(this.editor, this.sessionId)

  • add lastAppliedChange in editor to set up collaboration socket

  • When there's change happened ,register change callback --> to collaboration.service

  ngOnInit() {
    this.route.params
     .subscribe(params => {
       this.sessionId = params['id'];
       this.initEditor();
     });
  }
initEditor(){
    this.editor = ace.edit("editor");
    this.editor.setTheme("ace/theme/eclipse");
    this.resetEditor();
    this.editor.$blockScrolling = Infinity;
    // set up collaboration secket
    this.collaboration.init(this.editor, this.sessionId);
    this.editor.lastAppliedChange = null;
    
    // register changne callback
    this.editor.on('change', (e) => {
      console.log('editor change' + JSON.stringify(e));
      if (this.editor.lastAppliedChange != e) {
        this.collaboration.change(JSON.stringify(e));
      }
    })

  }

Collaboration Service

  • Send in both editor and sessionId to service

  • service listen to editor component, when there's a chagne message sent in, show the change in editor(in editor component) and send the chagne delta to Server (in collaboration service)

  init(editor: any, sessionId: string): void {
    this.collaborationSocket = io(window.location.origin, {query: "sessionId=" + sessionId});
    this.collaborationSocket.on('change', (delta: string) => {
      delta = JSON.parse(delta);
      editor.lastAppliedChange = delta;
      editor.getSession().getDocument().applyDeltas([delta]);
    });
  }
  • Also, send the change to Server by sockets
  change(delta: string): void {
    this.collaborationSocket.emit('change', delta);
  }
  • delta
editor change{"start":{"row":6,"column":4},"end":{"row":6,"column":5},"action":"insert","lines":["a"]}

Server side - editorSocketService

  • collaborations to record who is in this problem

  • receive message from collaboration "{query: "sessionId=" + sessionId}", and save into sessionId

  • save sessionId into socket.id (user)

  • if sessionId is the first one which means not in collaboration, creates a new collaboration oject with participants

  • If the sessionId is in collaboration, add sockt.id into participants

module.exports = function(io){
    //collaboration sessions
    const collaborations = {};
    // map form socketId to sessionId
    const socketIdToSessionId = {};

    io.on('connection', (socket) => {
        const sessionId = socket.handshake.query['sessionId'];
        socketIdToSessionId[socket.id] = sessionId;

        if (!sessionId in collaborations) {
            collaborations[sessionId] = {
                'participants':[]
            };
        }
        collaborations[sessionId]['participants'].push(socket.id);
    });
}
  • If Service revceive change, save the sessionId and array of participants. Then, send the changes to all participants.
  socket.on('change', delta => {
      const sessionId = socketIdToSessionId[socket.id];
      if (sessionId in collaborations){
          const participants = collaborations[sessionId]['participants'];
          for (let participant of participants) {
              if (socket.id !== participant){
                  io.to(participant).emit('change', delta)
              }
          }
      } else {
          console.error('error')
      }
  });

Store and restore socket session with Redis

  • collaboration service, when user
  • 從connection的時候就先去collaborations看有沒有這個sessionId
  • 如果 collaborations 裡面沒有, 要從Redis裡面拿(restoreBuffer)
  • 拿到 sessionId,到collections裡面看有沒有,如果沒有,直接說“沒有”
  • 如果有,直接到collections內拿記錄下來的"cachedInstructions:
  • 然後把 instrcutions裡面從頭到尾把所有變化讀取, [0] "change" [1] delta
  restoreBuffer():void{
    this.collaborationSocket.emit('restoreBuffer');
  }
  • editor component
this.collaboration.restoreBuffer();

Install Redis

  • In Linus
wget http://download.redis.io/releases/redis-3.2.6.tar.gz
tar xzf redis-3.2.6.tar.gz
cd redis-3.2.6
make
sudo make install
cd utils
sudo ./install_server.sh`
  • oj-server
npm install --save redis
  • build up a dir modules -> redisClient.js
mkdir modules
touch modules/redisClient.js

redisClient

const redis = require('redis');
const client = redis.createClient();

function set(key, value, callback) {
    client.set(key, value, function(err, res) {
        if (err) {
            console.log(err);
            return;
        }
        callback(res);
    });
}

function get(key, callback) {
    client.get(key, function(err, res) {
        if (err) {
            console.log(err);
            return;
        }
        callback(res);
    });
}

function expire(key, timeInSeconds) {
    client.expire(key, timeInSeconds);
}

function quit() {
    client.quit();
}

module.exports = {
    get,
    set,
    expire,
    quit,
    redisPrint: redis.print
}

Import and build up redis client in editor Socket Service

const redisClient = require('../modules/redisClient');
const TIMEOUT_IN_SECONDS = 3600;
  • module.exports
const sessionPath = '/temp_sessions/';

Logic in editorSocketService

Check if SessionId in collaborations

  • Find if user is the first one get into this problem by looking for collaborations (if there's another user) or for redis (in the timeset)
if (sessionId in collaborations) {
    collaborations[sessionId]['participants'].push(socket.id);
} else {
  redisClient.get(sessionPath + '/' + sessionId, data => {
      if (data) { // there is data in radis
          console.log('session terminated perviously, pulling back from redis');
          collaborations[sessionId] = {
              'cachaedInstructions' : JSON.parse(data),
              'participants': []
          }
      } else { // create a new collaboration
          console.log('creating new session');
          collaborations[sessionId] = {
              'cachaedInstructions' : [],
              'participants': []  
          }
      }
      collaborations[sessionId]['participants'].push(socket.id);
  });
}

There's change happen

  • Add a cachaedInstructions if there is a sessionId in collaborations when there is a change message
socket.on('change', delta => {
    const sessionId = socketIdToSessionId[socket.id];
    if(sessionId in collaborations){
        collaborations[sessionId]['cachaedInstructions'].push(['change', delta, Date.now()]);
    }

Client call restore Buffer (Need to show data)

  • When Client side call restore Buffer, emit out all contents saved in redis
  • instruction[0] : change
  • instruction[1] : content
  • instruction 就是紀錄每一行的動作
socket.on('restoreBuffer', () => {
    const sessionId = socketIdToSessionId[socket.id];
    if (sessionId in collaborations) {
        const instructions = collaborations[sessionId]['cachaedInstructions'];
        for (let instruction of instructions) {
            socket.emit(instruction[0], instruction[1]);
        }
    }
});

User disconnect

  • When disconnect, save content in radis
  • Remove user's sessionId from participants
  • See if the last user lefts (participants.length === 0)
socket.on('disconnrect', () => {
  const sessionId = socketIdToSessionId[socket.id];
  let foundAndRemove = false;
  if (sessionId in collaborations) {
      const participants = collaborations[sessionId]['participants'];
    const index = participants.indexOf(socket.id);
      if (index >= 0){
          participants.slice(index, 1);
          foundAndRemove = true;
          if (participants.length === 0){ //last user
              const key = sessionPath + '/' + sessionId;
              const value = JSON.stringify(collaborations[sessionId]['cachaedInstructions']);
              redisClient.set(key, value, redisClient.redisPrint);
              redisClient.expire(key, TIMEOUT_IN_SECONDS);
              delete collaboraitons[sessionId];
          }
      }
  }
  if (!foundAndRemove) {
      console.error('warning');
  }
});

Week 4

  • Show Result on UI
  • Add build_and_run API call in Node.js Server
  • Build a web server with Flask
  • Install Docker
  • Create Dockerfile
  • Docker build/push/pull
  • Build Executor

Before Editting

-Copy the week2 code

cp -r week3 week4

Show RESULT on UI

Step1 : In editor, we need to display compile and execute output. Therefore, we need to add a div right after the ACE editor in editor component.

Client Side

  • In Editor Component htnl and ts
    <div>
        {{output}}
    </div>
   'Python': `class Solution:
   def example():
       # Write your Python code here`
  };

output: string = '';

Step2 : Build and Run are performed on server side. On the client side, we just make a service call and display the results. Here we call DataService to make the call.

Step3: Add buildAndRun in DataService. Basically, it is just a post request to Node.js server then getting results. The target of post request is /api/vi/results

  • NoteL client doesn't know about executor, to client, it's only talking to the Node.js server

  • Send usercode and language datas to Server side (by DataService)

buildAndRun(data): Promise<any> {
    const options = { headers: new HttpHeaders({ 'Content-Type': 'application/json'})};
    return this.httpClient.post('api/v1/build_and_run', data, options)
      .toPromise()
      .then(res => {
        console.log(res);
        return res;
      })
      .catch(this.handleError);
  }
  • Import DataService in editor component and send out the userCodes and language data
  • Also add in constructor
  • data as a JSON object sent to server side
  submit(): void {
    const userCodes = this.editor.getValue();
    const data = {
      userCodes: userCodes,
      lang: this.language.toLocaleLowerCase()
    };
  
    this.dataService.buildAndRun(data)
      .then(res => this.output = res.text);
  }
private dataService: DataService

Server Side

  • Handle the Router in rest.js
router.post('/build_and_run', jsonParser, (req, res) => {
    const userCodes = req.body.userCodes;
    const lang = req.body.lang;
    console.log('lang: ', lang, 'usercode: ', userCodes);
    res.json({'text': 'hello from nodejs'});
});

Send the UserCodes from Node server side (now as a client side) to the Online Judger system!

Install node-rest-client

npm install --save node-rest-client
  • In rest.js import restClient and register
const nodeRestClient = require('node-rest-client').Client;
const restClient = new nodeRestClient();

EXECUTOR_SERVER_URL = 'http://localhost:5000/build_and_run';

restClient.registerMethod('build_and_run', EXECUTOR_SERVER_URL, 'POST');
  • Modify the build_and_run rout
router.post('/build_and_run', jsonParser, (req, res) => {
    const userCodes = req.body.userCodes;
    const lang = req.body.lang;
    console.log('lang: ', lang, 'usercode: ', userCodes);
   restClient.methods.build_and_run(
       {
           data: {code: userCodes, lang: lang},
           headers: { 'Content-Type': 'application/json'}
       },
       (data, response) => {
           // build: xxx ; run: xxx
           const text = `Build output: ${data['build']}. Execute Output: ${data['run']}`;
           data['text'] = text;
           res.json(data);
       }
   );
});

Executer Server (Pyton3)

  • Build up a Execute Server
mkdir executor
cd executor

Install pip3 for python3

sudo apt-get update
sudo apt-get -y install python3-pip
sudo pip3 install Flask

Add a requirements.txt for dependencies

touch requiremnet.txt

Flast
  • When you went to anoter developeing enviroment, could automatically intsall
sudo pip3 install -r requirement

Executor_server.py

  • Add a Flash server, test the connection in localhost:5000 (default)
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'hello world'
if __name__ == '__main__':
    app.run(debug = True)

@app.route('/build_and_run', methods = ['POST'])
def build_and_run():
    return
if __name__ == '__main__':
    app.run(debug = True)       
  • Transfer the output to JSON
import json
from flask import Flask
app = Flask(__name__)
from flask import jsonify
from flask import request
  • Receive data from Node Server
@app.route('/build_and_run', methods=['POST'])
def build_and_run():
    data = request.get_json()
    if 'code' not in data or 'lang' not in data:
        return 'You should provide "code" and "lang"'
    code = data['code']
    lang = data['lang']
    print("API got called with code: %s in %s" % (code, lang))
    return jsonify({'build': 'build jajaja', 'run': 'run from oajsfoaij'})

Docker

  • Install Docker
curl -fsSL https://get.docker.com/ | sh

Setup docker permission:

sudo usermod -aG docker $(whoami)

(you need to logout and login again after set permission)

To start docker when the system boots: sudo systemctl enable docker
  • Dockerfile
FROM ubuntu:16.04
MAINTAINER Kevin Hsu
RUN apt-get update
RUN apt-get install -y openjdk-8-jdk
RUN apt-get install -y python3
  • Build Docker
sudo docker build -t weichienhsu/coj_project
docker login
docker push weichienhsu/coj_project
  • Docker Package
pip3 install docker
  • executor_utils.py
import docker
import os
import shutil
import uuid

from docker.errors import APIError
from docker.errors import ContainerError
from docker.errors import ImageNotFound
  • .gitignore
*.pyc
  • For Docker: source file names / binary names/ build commands/ execute commands
SOURCE_FILE_NAMES = {
    "java": "Example.java",
    "python": "example.py"
}
BINARY_NAMES = {
    "java": "Example",
    "python": "example.py"
}
BUILD_COMMANDS = {
    "java": "javac",
    "python": "python3"
}
EXECUTE_COMMANDS = {
    "java": "java",
    "python": "python3"
}
  • Create a file tmp to save current_dir
CURRENT_DIR = os.path.dirname(os.path.relpath(__file__))
IMAGE_NAME = 'weichienhsu/coj_project'
client = docker.from_env()

TEMP_BUILD_DIR = "%s/tmp/" % CURRENT_DIR
CONTAINER_NAME = "%s:latest" % IMAGE_NAME
  • Load image: first from client, if not found, pull from docker
  • for I/O we always need to try and expect
def load_image():
try:
    client.images.get(IMAGE_NAME)
    print("Image exists locally")
except ImageNotFound:
    print('image not found locally. lodaing from docker')
    client.images.pull(IMAGE_NAME)
except APIError:
    print('docker hub go die')
    return
print('image loaded')
  • Make a directory
def make_dir(dir):
    try:
        os.mkdir(dir)
    except OSError:
        print('go die')
  • Build and run function
def build_and_run(code, lang):
    result = {'build': None, 'run': None, 'error': None}
    source_file_parent_dir_name = uuid.uuid4()
    source_file_host_dir = "%s/%s" % (TEMP_BUILD_DIR, source_file_parent_dir_name)
    source_file_guest_dir = "/test/%s" % (source_file_parent_dir_name)
    make_dir(source_file_host_dir)
    
    with open("%s/%s" %(source_file_host_dir, SOURCE_FILE_NAMES[lang]), 'w') as source_file:
        source_file.write(code)
    try:
        client.containers.run(
            image=IMAGE_NAME,
            command="%s %s" % (BUILD_COMMANDS[lang], SOURCE_FILE_NAMES[lang]),
            volumes={source_file_host_dir: {'bind': source_file_guest_dir, 'mode': 'rw'}},
            working_dir=source_file_guest_dir
        )
        print('source built')
        result['build'] = 'OK'
    except ContainerError as e:
        result['build'] = str(e.stderr, 'utf-8')
        shutil.rmtree(source_file_host_dir)
        return result
    try:
        log = client.containers.run(
            image=IMAGE_NAME,
            command="%s %s" % (EXECUTE_COMMANDS[lang], BINARY_NAMES[lang]),
            volumes={source_file_host_dir: {'bind': source_file_guest_dir, 'mode': 'rw'}},
            working_dir=source_file_guest_dir
        )
        log = str(log, 'utf-8')
        result['run'] = log
    except ContainerError as e:
        result['run'] = str(e.stderr, 'utf-8')
        shutil.rmtree(source_file_host_dir)
        return result
    shutil.rmtree(source_file_host_dir)
    return result
  • For executor_server
  • Import executor_utils
import executor_utils as eu
    # return jsonify({'build': 'build jajaja', 'run': 'run from oajsfoaij'})
    result = eu.build_and_run(code, lang)
    return jsonify(result)
  • init the loading from eu
if __name__ == '__main__':
    eu.load_image()
    app.run(debug=True)
  • If Couldn't find docker module
pip install docker

Production

  • If frontend didn't change anymore, you could build a production version.

  • There is no .map file and you couldn't debug on browser

  • In oj-client

ng build --prod


Week5 Nginx - Load Balance

  • Script to settle all server and executer missions
  • launcher.sh
  • Remove executing localhost at first
  • Start redis
  • run server & -> keep runing
  • run executor &
  • echo
  • presskey to terminate processes
#! /bin/bash

fuser -k 3000/tcp
fuser -k 5000/tcp

service redis_6379 start

cd ./oj-server
nodemon server.js &

cd ../executor
pip3 install -r requirements.txt
python3 executor_server.py &

echo "=============================="
read -p "PRESS [enter] to terminate processes." PRESSKEY

fuser -k 3000/tcp
fuser -k 5000/tcp
service redis_6379 stop
  • bash ./launcher.sh

Install Nginx

deb http://nginx.org/packages/ubuntu/ venial nginx

deb-src http://nginx.org/packages/ubuntu/ xenial nginx

sudo apt-get update
sudo apt-get install nginx

sudo service nginx start

Execute

  • oj-server:
nodemon server.js
  • executor:
python3 executor_server.py
  • Redis:
redis-server
  • Docker
sudo ducker run weichienhsu/coj-project

Developer:

  • oj-client:
npm install
  • oj-server:
npm install

About

Collaborative Online Edition System | Angular2+ & Node.js |

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 47.8%
  • JavaScript 26.1%
  • HTML 15.0%
  • Python 7.1%
  • CSS 3.0%
  • Shell 0.7%
  • Dockerfile 0.3%