mirror of https://github.com/OpenVidu/openvidu.git
openvidu-sample-app added to repo
parent
6769075de2
commit
a4d79b6541
|
@ -0,0 +1,255 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
backend/openvidu-sample-app/target/*
|
||||
backend/openvidu-sample-app/src/main/resources/static/*
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
cd frontend
|
||||
ng build --output-path ./../backend/openvidu-sample-app/src/main/resources/static
|
||||
cd ../backend/openvidu-sample-app
|
||||
mvn clean package
|
||||
java -jar target/openvidu-sample-app-0.0.1-SNAPSHOT.war
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1 @@
|
|||
/target/
|
|
@ -0,0 +1,126 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>openvidu</groupId>
|
||||
<artifactId>openvidu-sample-app</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>openvidu-sample-app</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<start-class>openvidu.openvidu_sample_app.App</start-class>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
||||
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<!-- ONLY ON DEVELOPMENT -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>springloaded</artifactId>
|
||||
<version>1.2.6.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<!-- ONLY ON DEVELOPMENT -->
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>${start-class}</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<webResources>
|
||||
<resource>
|
||||
<directory>src/main/ebextensions</directory>
|
||||
<targetPath>.ebextensions</targetPath>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</webResources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-validator</groupId>
|
||||
<artifactId>commons-validator</artifactId>
|
||||
<version>1.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,30 @@
|
|||
package openvidu.openvidu_sample_app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableWebSocket
|
||||
public class App
|
||||
{
|
||||
public static void main( String[] args )
|
||||
{
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebMvcConfigurer corsConfigurer() {
|
||||
return new WebMvcConfigurerAdapter() {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedOrigins("*");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package openvidu.openvidu_sample_app;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import openvidu.openvidu_sample_app.user.UserRepository;
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
import openvidu.openvidu_sample_app.lesson.LessonRepository;
|
||||
import openvidu.openvidu_sample_app.lesson.Lesson;
|
||||
|
||||
@Controller
|
||||
public class DatabaseInitializer implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private LessonRepository lessonRepository;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
|
||||
//Sample users
|
||||
User user1 = new User("student1@gmail.com", "pass", "Student Imprudent", "ROLE_STUDENT");
|
||||
User user2 = new User("student2@gmail.com", "pass", "Student Concludent", "ROLE_STUDENT");
|
||||
User user3 = new User("teacher@gmail.com", "pass", "Teacher Cheater", "ROLE_TEACHER");
|
||||
|
||||
//Saving users
|
||||
userRepository.save(user1);
|
||||
userRepository.save(user2);
|
||||
userRepository.save(user3);
|
||||
|
||||
//Sample lessons
|
||||
Lesson c1 = new Lesson("Lesson number 1", user3);
|
||||
Lesson c2 = new Lesson("Lesson number 2", user3);
|
||||
|
||||
c1.getAttenders().add(user1);
|
||||
c1.getAttenders().add(user2);
|
||||
c1.getAttenders().add(user3);
|
||||
|
||||
c2.getAttenders().add(user1);
|
||||
c2.getAttenders().add(user3);
|
||||
|
||||
//Saving lessons
|
||||
lessonRepository.save(c1);
|
||||
lessonRepository.save(c2);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package openvidu.openvidu_sample_app.lesson;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
|
||||
@Entity
|
||||
public class Lesson {
|
||||
|
||||
public interface SimpleCourseList {}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@JsonView(SimpleCourseList.class)
|
||||
private long id;
|
||||
|
||||
@JsonView(SimpleCourseList.class)
|
||||
private String title;
|
||||
|
||||
@ManyToOne
|
||||
private User teacher;
|
||||
|
||||
@ManyToMany
|
||||
private Set<User> attenders;
|
||||
|
||||
public Lesson() {}
|
||||
|
||||
public Lesson(String title, User teacher) {
|
||||
this.title = title;
|
||||
this.teacher = teacher;
|
||||
this.attenders = new HashSet<>();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public User getTeacher() {
|
||||
return teacher;
|
||||
}
|
||||
|
||||
public void setTeacher(User teacher) {
|
||||
this.teacher = teacher;
|
||||
}
|
||||
|
||||
public Set<User> getAttenders() {
|
||||
return attenders;
|
||||
}
|
||||
|
||||
public void setAttenders(Set<User> attenders) {
|
||||
this.attenders = attenders;
|
||||
}
|
||||
|
||||
//To make 'user.getLesson().remove(lesson)' possible
|
||||
@Override
|
||||
public boolean equals(Object other){
|
||||
if (other == null) return false;
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof Lesson))return false;
|
||||
Lesson otherCourse = (Lesson)other;
|
||||
return (otherCourse.id == this.id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
package openvidu.openvidu_sample_app.lesson;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
import openvidu.openvidu_sample_app.user.UserComponent;
|
||||
import openvidu.openvidu_sample_app.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api-lessons")
|
||||
public class LessonController {
|
||||
|
||||
@Autowired
|
||||
private LessonRepository lessonRepository;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private UserComponent user;
|
||||
|
||||
private class AddAttendersResponse {
|
||||
public Collection<User> attendersAdded;
|
||||
public Collection<User> attendersAlreadyAdded;
|
||||
public Collection<String> emailsInvalid;
|
||||
public Collection<String> emailsValidNotRegistered;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Collection<Lesson>> getLessons(@PathVariable(value="id") String id){
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
long id_i = -1;
|
||||
try{
|
||||
id_i = Long.parseLong(id);
|
||||
}catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
Set<Long> s = new HashSet<>();
|
||||
s.add(id_i);
|
||||
Collection<User> users = userRepository.findAll(s);
|
||||
Collection<Lesson> lessons = new HashSet<>();
|
||||
lessons = lessonRepository.findByAttenders(users);
|
||||
return new ResponseEntity<>(lessons ,HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/lesson/{id}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Lesson> getLesson(@PathVariable(value="id") String id){
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
long id_i = -1;
|
||||
try{
|
||||
id_i = Long.parseLong(id);
|
||||
}catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
Lesson lesson = lessonRepository.findOne(id_i);
|
||||
return new ResponseEntity<>(lesson ,HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/new", method = RequestMethod.POST)
|
||||
public ResponseEntity<Lesson> newLesson(@RequestBody Lesson lesson) {
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
User userLogged = user.getLoggedUser();
|
||||
|
||||
if (!user.hasRoleTeacher()){
|
||||
// Students are not authorized to add new lessons
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Updating lesson ('teacher', 'attenders')
|
||||
lesson.setTeacher(userLogged);
|
||||
lesson.getAttenders().add(userLogged);
|
||||
|
||||
// Saving lesson
|
||||
lessonRepository.save(lesson);
|
||||
lessonRepository.flush();
|
||||
|
||||
lesson = lessonRepository.findOne(lesson.getId());
|
||||
return new ResponseEntity<>(lesson, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/edit", method = RequestMethod.PUT)
|
||||
public ResponseEntity<Lesson> modifyLesson(@RequestBody Lesson lesson) {
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(lesson.getId());
|
||||
|
||||
checkAuthorization(c, c.getTeacher());
|
||||
|
||||
//Modifying the lesson attributes
|
||||
c.setTitle(lesson.getTitle());
|
||||
|
||||
//Saving the modified lesson
|
||||
lessonRepository.save(c);
|
||||
return new ResponseEntity<>(c, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/delete/{lessonId}", method = RequestMethod.DELETE)
|
||||
public ResponseEntity<Lesson> deleteLesson(@PathVariable(value="lessonId") String lessonId) {
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
long id_lesson = -1;
|
||||
try{
|
||||
id_lesson = Long.parseLong(lessonId);
|
||||
}catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(id_lesson);
|
||||
|
||||
checkAuthorization(c, c.getTeacher());
|
||||
|
||||
//Removing the deleted lesson from its attenders
|
||||
Collection<Lesson> lessons = new HashSet<>();
|
||||
lessons.add(c);
|
||||
Collection<User> users = userRepository.findByLessons(lessons);
|
||||
for(User u: users){
|
||||
u.getLessons().remove(c);
|
||||
}
|
||||
userRepository.save(users);
|
||||
c.getAttenders().clear();
|
||||
|
||||
lessonRepository.delete(c);
|
||||
return new ResponseEntity<>(c, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/edit/add-attenders/lesson/{lessonId}", method = RequestMethod.PUT)
|
||||
public ResponseEntity<AddAttendersResponse> addAttenders(
|
||||
@RequestBody String[] attenderEmails,
|
||||
@PathVariable(value="lessonId") String lessonId)
|
||||
{
|
||||
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
long id_lesson = -1;
|
||||
try{
|
||||
id_lesson = Long.parseLong(lessonId);
|
||||
}catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(id_lesson);
|
||||
|
||||
checkAuthorization(c, c.getTeacher());
|
||||
|
||||
//Strings with a valid email format
|
||||
Set<String> attenderEmailsValid = new HashSet<>();
|
||||
//Strings with an invalid email format
|
||||
Set<String> attenderEmailsInvalid = new HashSet<>();
|
||||
//Strings with a valid email format but no registered in the application
|
||||
Set<String> attenderEmailsNotRegistered = new HashSet<>();
|
||||
|
||||
EmailValidator emailValidator = EmailValidator.getInstance();
|
||||
|
||||
for (int i = 0; i < attenderEmails.length; i++){
|
||||
if (emailValidator.isValid(attenderEmails[i])) {
|
||||
attenderEmailsValid.add(attenderEmails[i]);
|
||||
} else {
|
||||
attenderEmailsInvalid.add(attenderEmails[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<User> newPossibleAttenders = userRepository.findByNameIn(attenderEmailsValid);
|
||||
Collection<User> newAddedAttenders = new HashSet<>();
|
||||
Collection<User> alreadyAddedAttenders = new HashSet<>();
|
||||
|
||||
for (String s : attenderEmailsValid){
|
||||
if (!this.userListContainsEmail(newPossibleAttenders, s)){
|
||||
attenderEmailsNotRegistered.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
for (User attender : newPossibleAttenders){
|
||||
boolean newAtt = true;
|
||||
if (!attender.getLessons().contains(c)) attender.getLessons().add(c); else newAtt = false;
|
||||
if (!c.getAttenders().contains(attender)) c.getAttenders().add(attender); else newAtt = false;
|
||||
if (newAtt) newAddedAttenders.add(attender); else alreadyAddedAttenders.add(attender);
|
||||
}
|
||||
|
||||
//Saving the attenders (all of them, just in case a field of the bidirectional relationship is missing in a Lesson or a User)
|
||||
userRepository.save(newPossibleAttenders);
|
||||
//Saving the modified lesson
|
||||
lessonRepository.save(c);
|
||||
|
||||
AddAttendersResponse customResponse = new AddAttendersResponse();
|
||||
customResponse.attendersAdded = newAddedAttenders;
|
||||
customResponse.attendersAlreadyAdded = alreadyAddedAttenders;
|
||||
customResponse.emailsInvalid = attenderEmailsInvalid;
|
||||
customResponse.emailsValidNotRegistered = attenderEmailsNotRegistered;
|
||||
|
||||
return new ResponseEntity<>(customResponse, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/edit/delete-attenders", method = RequestMethod.PUT)
|
||||
public ResponseEntity<Set<User>> deleteAttenders(@RequestBody Lesson lesson) {
|
||||
if (!this.userIsLogged()){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(lesson.getId());
|
||||
|
||||
checkAuthorization(c, c.getTeacher());
|
||||
|
||||
Set<Lesson> setLesson = new HashSet<>();
|
||||
setLesson.add(c);
|
||||
Collection<User> lessonAttenders = userRepository.findByLessons(setLesson);
|
||||
|
||||
for (User attender : lessonAttenders){
|
||||
if (!lesson.getAttenders().contains(attender)){
|
||||
attender.getLessons().remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
userRepository.save(lessonAttenders);
|
||||
|
||||
//Modifying the lesson attenders
|
||||
c.setAttenders(lesson.getAttenders());
|
||||
//Saving the modified lesson
|
||||
lessonRepository.save(c);
|
||||
return new ResponseEntity<>(c.getAttenders(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
//Login checking method for the backend
|
||||
private boolean userIsLogged(){
|
||||
if (!user.isLoggedUser()) {
|
||||
System.out.println("Not user logged");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Authorization checking for editing and deleting lessons (the teacher must own the Lesson)
|
||||
private ResponseEntity<Object> checkAuthorization(Object o, User u){
|
||||
if(o == null){
|
||||
//The object does not exist
|
||||
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
|
||||
}
|
||||
if(!this.user.getLoggedUser().equals(u)){
|
||||
//The teacher is not authorized to edit it if he is not its owner
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Checks if a User collection contains a user with certain email
|
||||
private boolean userListContainsEmail(Collection<User> users, String email){
|
||||
boolean isContained = false;
|
||||
for (User u : users){
|
||||
if (u.getName().equals(email)) {
|
||||
isContained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isContained;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package openvidu.openvidu_sample_app.lesson;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
|
||||
public interface LessonRepository extends JpaRepository<Lesson, Long> {
|
||||
|
||||
public Collection<Lesson> findByAttenders(Collection<User> users);
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package openvidu.openvidu_sample_app.security;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
import openvidu.openvidu_sample_app.user.UserComponent;
|
||||
|
||||
/**
|
||||
* This class is used to provide REST endpoints to logIn and logOut to the
|
||||
* service. These endpoints are used by Angular 2 SPA client application.
|
||||
*
|
||||
* NOTE: This class is not intended to be modified by app developer.
|
||||
*/
|
||||
@RestController
|
||||
public class LoginController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoginController.class);
|
||||
|
||||
@Autowired
|
||||
private UserComponent userComponent;
|
||||
|
||||
@RequestMapping("/api-logIn")
|
||||
public ResponseEntity<User> logIn() {
|
||||
|
||||
System.out.println("Logging in...");
|
||||
|
||||
if (!userComponent.isLoggedUser()) {
|
||||
|
||||
System.out.println("Not user logged");
|
||||
|
||||
log.info("Not user logged");
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
} else {
|
||||
User loggedUser = userComponent.getLoggedUser();
|
||||
|
||||
System.out.println("Logged as " + loggedUser.getName());
|
||||
|
||||
log.info("Logged as " + loggedUser.getName());
|
||||
return new ResponseEntity<>(loggedUser, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/api-logOut")
|
||||
public ResponseEntity<Boolean> logOut(HttpSession session) {
|
||||
|
||||
System.out.println("Logging out...");
|
||||
|
||||
if (!userComponent.isLoggedUser()) {
|
||||
log.info("No user logged");
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
} else {
|
||||
session.invalidate();
|
||||
log.info("Logged out");
|
||||
return new ResponseEntity<>(true, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package openvidu.openvidu_sample_app.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Security configuration. In this class several aspects
|
||||
* related to security can be configured:
|
||||
* Security behavior: Login method, session management, CSRF, etc..
|
||||
* Authentication provider: Responsible to authenticate users. In this
|
||||
* example, we use an instance of UserRepositoryAuthProvider, that authenticate
|
||||
* users stored in a Spring Data database.
|
||||
* URL Access Authorization: Access to http URLs depending on Authenticated
|
||||
* vs anonymous users and also based on user role.
|
||||
*
|
||||
*
|
||||
* NOTE: The only part of this class intended for app developer customization is
|
||||
* the method configureUrlAuthorization. App developer should
|
||||
* decide what URLs are accessible by what user role.
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
public UserRepositoryAuthProvider userRepoAuthProvider;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
|
||||
configureUrlAuthorization(http);
|
||||
|
||||
// Disable CSRF protection (it is difficult to implement with ng2)
|
||||
http.csrf().disable();
|
||||
|
||||
// Use Http Basic Authentication
|
||||
http.httpBasic();
|
||||
|
||||
// Do not redirect when logout
|
||||
http.logout().logoutSuccessHandler((rq, rs, a) -> {
|
||||
});
|
||||
}
|
||||
|
||||
private void configureUrlAuthorization(HttpSecurity http) throws Exception {
|
||||
|
||||
// APP: This rules have to be changed by app developer
|
||||
|
||||
// URLs that need authentication to access to it
|
||||
//Lessons API
|
||||
http.authorizeRequests().antMatchers(HttpMethod.GET, "/api-lessons/**").hasAnyRole("TEACHER", "STUDENT");
|
||||
http.authorizeRequests().antMatchers(HttpMethod.POST, "/api-lessons/**").hasRole("TEACHER");
|
||||
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/api-lessons/**").hasRole("TEACHER");
|
||||
http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/api-lessons/**").hasRole("TEACHER");
|
||||
|
||||
http.authorizeRequests().antMatchers(HttpMethod.POST, "/api-sessions/**").authenticated();
|
||||
|
||||
// Other URLs can be accessed without authentication
|
||||
http.authorizeRequests().anyRequest().permitAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
|
||||
// Database authentication provider
|
||||
auth.authenticationProvider(userRepoAuthProvider);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package openvidu.openvidu_sample_app.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import openvidu.openvidu_sample_app.user.UserRepository;
|
||||
import openvidu.openvidu_sample_app.user.UserComponent;
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
/**
|
||||
* This class is used to check http credentials against database data. Also it
|
||||
* is responsible to set database user info into userComponent, a session scoped
|
||||
* bean that holds session user information.
|
||||
*
|
||||
* NOTE: This class is not intended to be modified by app developer.
|
||||
*/
|
||||
@Component
|
||||
public class UserRepositoryAuthProvider implements AuthenticationProvider {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private UserComponent userComponent;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
|
||||
String username = authentication.getName();
|
||||
String password = (String) authentication.getCredentials();
|
||||
|
||||
User user = userRepository.findByName(username);
|
||||
|
||||
if (user == null) {
|
||||
throw new BadCredentialsException("User not found");
|
||||
}
|
||||
|
||||
if (!new BCryptPasswordEncoder().matches(password, user.getPasswordHash())) {
|
||||
|
||||
throw new BadCredentialsException("Wrong password");
|
||||
} else {
|
||||
|
||||
userComponent.setLoggedUser(user);
|
||||
|
||||
List<GrantedAuthority> roles = new ArrayList<>();
|
||||
for (String role : user.getRoles()) {
|
||||
roles.add(new SimpleGrantedAuthority(role));
|
||||
}
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(username, password, roles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authenticationObject) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package openvidu.openvidu_sample_app.session_manager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import openvidu.openvidu_sample_app.lesson.Lesson;
|
||||
import openvidu.openvidu_sample_app.lesson.LessonRepository;
|
||||
import openvidu.openvidu_sample_app.user.User;
|
||||
import openvidu.openvidu_sample_app.user.UserComponent;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api-sessions")
|
||||
public class SessionController {
|
||||
|
||||
@Autowired
|
||||
private LessonRepository lessonRepository;
|
||||
|
||||
@Autowired
|
||||
private UserComponent user;
|
||||
|
||||
private Map<Long, String> sessionIdMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final String OPENVIDU_URL = "https://localhost:8443/";
|
||||
private final String SECRET ="MY_SECRET";
|
||||
private HttpClient myHttpClient;
|
||||
|
||||
public SessionController() {
|
||||
try {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("OPENVIDUAPP", SECRET);
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
|
||||
SSLContextBuilder builder = new SSLContextBuilder();
|
||||
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
|
||||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(),
|
||||
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
/*SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
|
||||
builder.build());*/
|
||||
this.myHttpClient = HttpClients.custom().setSSLSocketFactory(
|
||||
sslsf).setDefaultCredentialsProvider(provider).build();
|
||||
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/create-session", method = RequestMethod.POST)
|
||||
public ResponseEntity<String> createSession(@RequestBody String lessonId) throws Exception {
|
||||
|
||||
if (!this.userIsLogged()) {
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if(!user.hasRoleTeacher()) {
|
||||
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
long id_lesson = -1;
|
||||
try {
|
||||
id_lesson = Long.parseLong(lessonId);
|
||||
} catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(id_lesson);
|
||||
|
||||
if (!checkAuthorization(c, c.getTeacher())){
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if(this.sessionIdMap.get(id_lesson) != null) {
|
||||
|
||||
// If there's already a valid sessionId for this lesson, not necessary to ask for a new one
|
||||
return new ResponseEntity<>(this.sessionIdMap.get(id_lesson), HttpStatus.OK);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
String sessionId = this.openVidu.createSession();
|
||||
*/
|
||||
|
||||
HttpResponse response = myHttpClient.execute(new HttpGet(OPENVIDU_URL + "getSessionId"));
|
||||
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if ((statusCode == org.apache.http.HttpStatus.SC_OK) && (response.getEntity().getContentLength() > 0)){
|
||||
System.out.println("Returning a sessionId...");
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
|
||||
String sessionId = br.readLine();
|
||||
|
||||
this.sessionIdMap.put(id_lesson, sessionId);
|
||||
|
||||
return new ResponseEntity<>(sessionId, HttpStatus.OK);
|
||||
} else {
|
||||
System.out.println("Problems with openvidu-server");
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/generate-token", method = RequestMethod.POST)
|
||||
public ResponseEntity<JSONObject> generateToken(@RequestBody String lessonId) throws Exception {
|
||||
|
||||
if (!this.userIsLogged()) {
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
long id_lesson = -1;
|
||||
try{
|
||||
id_lesson = Long.parseLong(lessonId);
|
||||
}catch(NumberFormatException e){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
Lesson c = lessonRepository.findOne(id_lesson);
|
||||
|
||||
if (!checkAuthorizationUsers(c, c.getAttenders())){
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (this.sessionIdMap.get(id_lesson) == null){
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
String role = user.hasRoleTeacher() ? "PUBLISHER" : "SUBSCRIBER";
|
||||
|
||||
/*
|
||||
String token = this.openVidu.generateToken(sessionId, role);
|
||||
*/
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put(0, this.sessionIdMap.get(id_lesson));
|
||||
json.put(1, role);
|
||||
|
||||
HttpPost request = new HttpPost(OPENVIDU_URL + "newToken");
|
||||
StringEntity params = new StringEntity(json.toString());
|
||||
request.addHeader("content-type", "application/json");
|
||||
request.setEntity(params);
|
||||
|
||||
HttpResponse response = myHttpClient.execute(request);
|
||||
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if ((statusCode == org.apache.http.HttpStatus.SC_OK) && (response.getEntity().getContentLength() > 0)){
|
||||
System.out.println("Returning a sessionId and a token...");
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
|
||||
String token = br.readLine();
|
||||
|
||||
JSONObject responseJson = new JSONObject();
|
||||
responseJson.put(0, this.sessionIdMap.get(id_lesson));
|
||||
responseJson.put(1, token);
|
||||
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
} else {
|
||||
System.out.println("Problems with openvidu-server");
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean userIsLogged(){
|
||||
if (!user.isLoggedUser()) {
|
||||
System.out.println("Not user logged");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Authorization checking for creating or joining a certain lesson
|
||||
private boolean checkAuthorization(Object o, User u){
|
||||
return !(o == null || !this.user.getLoggedUser().equals(u));
|
||||
}
|
||||
|
||||
// Authorization checking for joining a session (the user must be an attender)
|
||||
private boolean checkAuthorizationUsers(Object o, Collection<User> users){
|
||||
return !(o == null || !users.contains(this.user.getLoggedUser()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package openvidu.openvidu_sample_app.user;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import openvidu.openvidu_sample_app.lesson.Lesson;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String nickName;
|
||||
|
||||
@JsonIgnore
|
||||
private String passwordHash;
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
private List<String> roles;
|
||||
|
||||
//It is ignored in order to avoid infinite recursiveness
|
||||
//This makes necessary another interaction with the database (after login to retrieve the lessons of the user)
|
||||
@JsonIgnore
|
||||
@ManyToMany(mappedBy="attenders")
|
||||
private Set<Lesson> lessons;
|
||||
|
||||
public User() {}
|
||||
|
||||
public User(String name, String password, String nickName, String... roles){
|
||||
this.name = name;
|
||||
this.passwordHash = new BCryptPasswordEncoder().encode(password);
|
||||
this.roles = new ArrayList<>(Arrays.asList(roles));
|
||||
this.nickName = nickName;
|
||||
this.lessons = new HashSet<>();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public List<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public String getNickName() {
|
||||
return nickName;
|
||||
}
|
||||
|
||||
public void setNickName(String nickName) {
|
||||
this.nickName = nickName;
|
||||
}
|
||||
|
||||
public Set<Lesson> getLessons() {
|
||||
return lessons;
|
||||
}
|
||||
|
||||
public void setLessons(Set<Lesson> lessons) {
|
||||
this.lessons = lessons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other){
|
||||
if (other == null) return false;
|
||||
if (other == this) return true;
|
||||
if (!(other instanceof User))return false;
|
||||
User otherUser = (User)other;
|
||||
return ((otherUser.id == this.id) && (otherUser.name.equals(this.name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package openvidu.openvidu_sample_app.user;
|
||||
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* This class is designed to manage the information for the user while he is
|
||||
* logged in the service. This object can be used in any other @Component
|
||||
* auto-wiring it as usual.
|
||||
*
|
||||
* Instances of this class are never sent to the user in any REST endpoint. It
|
||||
* can hold sensible information that can not be known in the client.
|
||||
*
|
||||
* NOTE: This class is intended to be extended by developer adding new
|
||||
* attributes. Current attributes can not be removed because they are used in
|
||||
* authentication procedures.
|
||||
*/
|
||||
|
||||
@Component
|
||||
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||
public class UserComponent {
|
||||
|
||||
private User user;
|
||||
|
||||
public User getLoggedUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setLoggedUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public boolean isLoggedUser() {
|
||||
return this.user != null;
|
||||
}
|
||||
|
||||
public boolean hasRoleTeacher() {
|
||||
return this.user.getRoles().contains("ROLE_TEACHER");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package openvidu.openvidu_sample_app.user;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api-users")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
//userData: [name, pass, nickName, role]
|
||||
@RequestMapping(value = "/new", method = RequestMethod.POST)
|
||||
public ResponseEntity<User> newUser(@RequestBody String[] userData) throws Exception {
|
||||
|
||||
System.out.println("Signing up a user...");
|
||||
|
||||
//If the email is not already in use
|
||||
if(userRepository.findByName(userData[0]) == null) {
|
||||
|
||||
//If the email has a valid format
|
||||
if (EmailValidator.getInstance().isValid(userData[0])){
|
||||
String role = (String) userData[3];
|
||||
System.out.println("Email and password are valid. Role of the new user: " + role);
|
||||
if(role == null){
|
||||
userData[3] = "ROLE_STUDENT";
|
||||
} else if (role.equals("teacher")) {
|
||||
userData[3] = "ROLE_TEACHER";
|
||||
} else {
|
||||
userData[3] = "ROLE_STUDENT";
|
||||
}
|
||||
User newUser = new User(userData[0], userData[1], userData[2], userData[3]);
|
||||
userRepository.save(newUser);
|
||||
return new ResponseEntity<>(newUser, HttpStatus.CREATED);
|
||||
}
|
||||
else {
|
||||
System.out.println("Email NOT valid");
|
||||
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
} else {
|
||||
System.out.println("Email already in use");
|
||||
return new ResponseEntity<>(HttpStatus.CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package openvidu.openvidu_sample_app.user;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import openvidu.openvidu_sample_app.lesson.Lesson;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long>{
|
||||
|
||||
public User findByName(String name);
|
||||
|
||||
public Collection<User> findByNameIn(Collection<String> names);
|
||||
|
||||
public Collection<User> findByLessons(Collection<Lesson> lessons);
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
spring.datasource.url=jdbc:mysql://localhost/full_teaching
|
||||
spring.datasource.username=ft-root
|
||||
spring.datasource.password=pass
|
||||
spring.datasource.driverClassName=com.mysql.jdbc.Driver
|
||||
spring.jpa.hibernate.ddl-auto: create-drop
|
||||
|
||||
server.port=5000
|
|
@ -0,0 +1,38 @@
|
|||
package openvidu.openvidu_sample_app;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest
|
||||
extends TestCase
|
||||
{
|
||||
/**
|
||||
* Create the test case
|
||||
*
|
||||
* @param testName name of the test case
|
||||
*/
|
||||
public AppTest( String testName )
|
||||
{
|
||||
super( testName );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the suite of tests being tested
|
||||
*/
|
||||
public static Test suite()
|
||||
{
|
||||
return new TestSuite( AppTest.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rigourous Test :-)
|
||||
*/
|
||||
public void testApp()
|
||||
{
|
||||
assertTrue( true );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "openvidu-sample-app"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"component": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
|
@ -0,0 +1,42 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
#System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
|
@ -0,0 +1,27 @@
|
|||
# OpenviduSampleApp
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.1.
|
||||
|
||||
## Development server
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
@ -0,0 +1,14 @@
|
|||
import { OpenviduSampleAppPage } from './app.po';
|
||||
|
||||
describe('openvidu-sample-app App', () => {
|
||||
let page: OpenviduSampleAppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new OpenviduSampleAppPage();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('app works!');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { browser, element, by } from 'protractor';
|
||||
|
||||
export class OpenviduSampleAppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"outDir": "../dist/out-tsc-e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"types":[
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
files: [
|
||||
{ pattern: './src/test.ts', watched: false }
|
||||
],
|
||||
preprocessors: {
|
||||
'./src/test.ts': ['@angular/cli']
|
||||
},
|
||||
mime: {
|
||||
'text/x-typescript': ['ts','tsx']
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
||||
? ['progress', 'coverage-istanbul']
|
||||
: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "openvidu-sample-app",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"author": "Pablo Fuente",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^2.4.0",
|
||||
"@angular/compiler": "^2.4.0",
|
||||
"@angular/core": "^2.4.0",
|
||||
"@angular/flex-layout": "^2.0.0-rc.1",
|
||||
"@angular/forms": "^2.4.0",
|
||||
"@angular/http": "^2.4.0",
|
||||
"@angular/material": "2.0.0-beta.2",
|
||||
"@angular/platform-browser": "^2.4.0",
|
||||
"@angular/platform-browser-dynamic": "^2.4.0",
|
||||
"@angular/router": "^3.4.0",
|
||||
"core-js": "^2.4.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"jquery": "^2.2.4",
|
||||
"openvidu-browser": "0.1.1",
|
||||
"rxjs": "^5.1.0",
|
||||
"zone.js": "^0.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.0.0-rc.1",
|
||||
"@angular/compiler-cli": "^2.4.0",
|
||||
"@types/jasmine": "2.5.38",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "~2.0.0",
|
||||
"jasmine-core": "~2.5.2",
|
||||
"jasmine-spec-reporter": "~3.2.0",
|
||||
"karma": "~1.4.1",
|
||||
"karma-chrome-launcher": "~2.0.0",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"karma-coverage-istanbul-reporter": "^0.2.0",
|
||||
"protractor": "~5.1.0",
|
||||
"ts-node": "~2.0.0",
|
||||
"tslint": "~4.4.2",
|
||||
"typescript": "~2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
beforeLaunch: function() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e/tsconfig.e2e.json'
|
||||
});
|
||||
},
|
||||
onPrepare() {
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
md-sidenav {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
md-sidenav-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
footer.page-footer {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer h2 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.sidenav-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header .fill-remaining-space {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
header #navbar-logo {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
footer ul {
|
||||
padding-left: 0;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<md-sidenav-container>
|
||||
|
||||
<md-sidenav #sidenav>
|
||||
<button md-button (click)="router.navigate(['/lessons']); sidenav.close()" class="sidenav-button">Lessons</button>
|
||||
<button md-button (click)="router.navigate(['/profile']); sidenav.close()" class="sidenav-button">Profile</button>
|
||||
<button md-button (click)="sidenav.close(); authenticationService.directLogOut()" class="sidenav-button">Logout</button>
|
||||
</md-sidenav>
|
||||
|
||||
<header *ngIf="!isVideoSessionUrl()">
|
||||
<md-toolbar color="primary" class="mat-elevation-z6">
|
||||
<button md-button routerLink="/" id="navbar-logo">
|
||||
OpenVidu Sample App
|
||||
</button>
|
||||
<span class="fill-remaining-space"></span>
|
||||
<div *ngIf="authenticationService.isLoggedIn()" fxLayout="row" fxShow="false" fxShow.gt-sm>
|
||||
<button md-button routerLink="/lessons">Lessons</button>
|
||||
<button md-button routerLink="/profile">Profile</button>
|
||||
<button md-button (click)="authenticationService.directLogOut()">LOGOUT</button>
|
||||
</div>
|
||||
<button *ngIf="authenticationService.isLoggedIn()" md-button fxHide="false" fxHide.gt-sm (click)="sidenav.open()">
|
||||
<md-icon>menu</md-icon>
|
||||
</button>
|
||||
</md-toolbar>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<footer *ngIf="!isVideoSessionUrl()" class="page-footer back-primary color-secondary mat-elevation-z5">
|
||||
<div class="container">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start start" fxLayoutAlign.xs="start">
|
||||
<div fxFlex="50%" fxFlex.xs="100%">
|
||||
<h2>This is a sample application</h2>
|
||||
<p class="grey-text text-lighten-4">Implementing a secure real time app with OpenVidu</p>
|
||||
</div>
|
||||
<div fxFlex="50%" fxFlex.xs="100%">
|
||||
<div fxLayout="row" fxLayoutAlign="end start" fxLayoutAlign.xs="start">
|
||||
<div fxFlex="50%">
|
||||
<h2>Technologies</h2>
|
||||
<ul>
|
||||
<li><a class="hover-link" href="https://angular.io/" target="_blank">Angular</a></li>
|
||||
<li><a class="hover-link" href="https://material.angular.io/" target="_blank">Angular Material</a></li>
|
||||
<li><a class="hover-link" href="https://spring.io/" target="_blank">Spring Framework</a></li>
|
||||
<li><a class="hover-link" href="https://www.kurento.org/" target="_blank">Kurento</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div fxFlex="50%">
|
||||
<h2>Connect</h2>
|
||||
<ul>
|
||||
<li><a class="hover-link" href="https://github.com/OpenVidu" target="_blank">GitHub repository</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</md-sidenav-container>
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it(`should have as title 'app works!'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app works!');
|
||||
}));
|
||||
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('app works!');
|
||||
}));
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AuthenticationService } from './services/authentication.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
constructor(private router: Router, private authenticationService: AuthenticationService) { }
|
||||
|
||||
isVideoSessionUrl() {
|
||||
return (this.router.url.substring(0, '/lesson/'.length) === '/lesson/');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
|
||||
import { routing } from './app.routing';
|
||||
|
||||
import { MaterialModule } from '@angular/material';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import 'hammerjs';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { PresentationComponent } from './components/presentation/presentation.component';
|
||||
import { DashboardComponent } from './components/dashboard/dahsboard.component';
|
||||
import { LessonDetailsComponent } from './components/lesson-details/lesson-details.component';
|
||||
import { ProfileComponent } from './components/profile/profile.component';
|
||||
import { VideoSessionComponent } from './components/video-session/video-session.component';
|
||||
import { ErrorMessageComponent } from './components/error-message/error-message.component';
|
||||
import { JoinSessionDialogComponent } from './components/dashboard/join-session-dialog.component';
|
||||
|
||||
import { AuthenticationService } from './services/authentication.service';
|
||||
import { UserService } from './services/user.service';
|
||||
import { LessonService } from './services/lesson.service';
|
||||
import { VideoSessionService } from './services/video-session.service';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
PresentationComponent,
|
||||
DashboardComponent,
|
||||
LessonDetailsComponent,
|
||||
ProfileComponent,
|
||||
VideoSessionComponent,
|
||||
ErrorMessageComponent,
|
||||
JoinSessionDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
MaterialModule,
|
||||
FlexLayoutModule.forRoot(),
|
||||
routing,
|
||||
],
|
||||
providers: [
|
||||
AuthenticationService,
|
||||
UserService,
|
||||
LessonService,
|
||||
VideoSessionService,
|
||||
AuthGuard,
|
||||
],
|
||||
entryComponents: [
|
||||
JoinSessionDialogComponent,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
|
@ -0,0 +1,40 @@
|
|||
import { ModuleWithProviders } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { PresentationComponent } from './components/presentation/presentation.component';
|
||||
import { DashboardComponent } from './components/dashboard/dahsboard.component';
|
||||
import { LessonDetailsComponent } from './components/lesson-details/lesson-details.component';
|
||||
import { ProfileComponent } from './components/profile/profile.component';
|
||||
import { VideoSessionComponent } from './components/video-session/video-session.component';
|
||||
|
||||
import { AuthGuard } from './auth.guard';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PresentationComponent,
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'lessons',
|
||||
component: DashboardComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'lesson-details/:id',
|
||||
component: LessonDetailsComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
component: ProfileComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'lesson/:id',
|
||||
component: VideoSessionComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
];
|
||||
|
||||
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
|
|
@ -0,0 +1,21 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Router, CanActivate } from '@angular/router';
|
||||
|
||||
import { AuthenticationService } from './services/authentication.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private router: Router, private authenticationService: AuthenticationService) { }
|
||||
|
||||
canActivate() {
|
||||
if (localStorage.getItem('login') && localStorage.getItem('rol') && this.authenticationService.isLoggedIn()) {
|
||||
// logged in so return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// not logged in so redirect to login page
|
||||
this.router.navigate(['']);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MdDialog, MdDialogRef, MdSnackBar } from '@angular/material';
|
||||
|
||||
import { Lesson } from '../../models/lesson';
|
||||
|
||||
import { LessonService } from '../../services/lesson.service';
|
||||
import { VideoSessionService } from '../../services/video-session.service';
|
||||
import { AuthenticationService } from '../../services/authentication.service';
|
||||
|
||||
import { JoinSessionDialogComponent } from './join-session-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.css'],
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
lessons: Lesson[];
|
||||
|
||||
addingLesson: false;
|
||||
lessonTitle: string;
|
||||
sumbitNewLesson: boolean;
|
||||
|
||||
constructor(
|
||||
private lessonService: LessonService,
|
||||
private videoSessionService: VideoSessionService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router,
|
||||
public snackBar: MdSnackBar,
|
||||
public dialog: MdDialog
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.authenticationService.checkCredentials();
|
||||
this.getLessons();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authenticationService.logOut();
|
||||
}
|
||||
|
||||
getLessons(): void {
|
||||
this.lessonService.getLessons(this.authenticationService.getCurrentUser()).subscribe(
|
||||
lessons => {
|
||||
console.log('User\'s lessons: ');
|
||||
console.log(lessons);
|
||||
this.lessons = lessons;
|
||||
this.authenticationService.updateUserLessons(this.lessons);
|
||||
},
|
||||
error => console.log(error));
|
||||
}
|
||||
|
||||
goToLesson(lesson: Lesson) {
|
||||
let dialogRef: MdDialogRef<JoinSessionDialogComponent>;
|
||||
dialogRef = this.dialog.open(JoinSessionDialogComponent);
|
||||
dialogRef.componentInstance.myReference = dialogRef;
|
||||
|
||||
dialogRef.afterClosed().subscribe(cameraOptions => {
|
||||
if (cameraOptions != null) {
|
||||
console.log('Joining session with options:');
|
||||
console.log(cameraOptions);
|
||||
this.videoSessionService.lesson = lesson;
|
||||
this.videoSessionService.cameraOptions = cameraOptions;
|
||||
this.router.navigate(['/lesson/' + lesson.id]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
goToLessonDetails(lesson: Lesson) {
|
||||
this.router.navigate(['/lesson-details/' + lesson.id]);
|
||||
}
|
||||
|
||||
newLesson() {
|
||||
this.sumbitNewLesson = true;
|
||||
this.lessonService.newLesson(new Lesson(this.lessonTitle)).subscribe(
|
||||
lesson => {
|
||||
console.log('New lesson added: ');
|
||||
console.log(lesson);
|
||||
this.lessons.push(lesson);
|
||||
this.authenticationService.updateUserLessons(this.lessons);
|
||||
this.sumbitNewLesson = false;
|
||||
this.snackBar.open('Lesson added!', undefined, { duration: 3000 });
|
||||
this.addingLesson = false;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.sumbitNewLesson = false;
|
||||
this.snackBar.open('There has been a problem...', undefined, { duration: 3000 });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createSession(lessonId: number){
|
||||
this.videoSessionService.createSession(lessonId).subscribe(
|
||||
response => {
|
||||
console.log(response.text());
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
generateToken(lessonId: number) {
|
||||
this.videoSessionService.generateToken(lessonId).subscribe(
|
||||
response => {
|
||||
console.log(response.text());
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
md-card {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
md-card md-icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.teacher {
|
||||
font-size: 12px;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<div *ngIf="!this.lessons" class="cssload-container">
|
||||
<div class="cssload-tube-tunnel"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="this.lessons" fxLayout="row" fxLayoutAlign="center center">
|
||||
<div class="div-inner-main" [style.xs]="{'width': '100%'}">
|
||||
|
||||
<div *ngIf="!addingLesson" fxLayout="row" fxLayoutAlign="center center">
|
||||
<div fxFlex="80%">MY LESSONS</div>
|
||||
<md-icon fxFlex="20%" fxLayoutAlign="end center" *ngIf="authenticationService.isTeacher()" (click)="addingLesson = true"
|
||||
[title]="'Add lesson'">add_circle_outline</md-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="addingLesson">
|
||||
<div>NEW LESSON</div>
|
||||
<form #newLessonForm (ngSubmit)="newLesson(); newLessonForm.reset()" [class.filtered]="sumbitNewLesson">
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Title" [(ngModel)]="lessonTitle" name="lessonTitle" id="lessonTitle" type="text" autocomplete="off"
|
||||
required>
|
||||
</md-input-container>
|
||||
<div class="block-btn">
|
||||
<button md-button type="submit" [disabled]="sumbitNewLesson">Send</button>
|
||||
<button md-button (click)="addingLesson = false; newLessonForm.reset()" [disabled]="sumbitNewLesson">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<md-card *ngFor="let lesson of lessons">
|
||||
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="10px">
|
||||
<span fxFlex="70%" class="title">{{lesson.title}}</span>
|
||||
<span fxFlex="70%" *ngIf="this.authenticationService.isStudent()" class="teacher">{{lesson.teacher.nickName}}</span>
|
||||
<md-icon fxFlex="15%" *ngIf="this.authenticationService.isTeacher()" (click)="goToLessonDetails(lesson)" [title]="'Modify lesson'">mode_edit</md-icon>
|
||||
<md-icon fxFlex="15%" (click)="goToLesson(lesson)" [title]="'Go to lesson!'">play_circle_filled</md-icon>
|
||||
</div>
|
||||
</md-card>
|
||||
|
||||
<div *ngIf="lessons.length === 0 && authenticationService.isStudent() && !addingLesson">
|
||||
<app-error-message [errorTitle]="'You do not have any lessons'" [errorContent]="'Your teacher must invite you'" [customClass]="'warning'"
|
||||
[closable]="false"></app-error-message>
|
||||
</div>
|
||||
|
||||
<div *ngIf="lessons.length === 0 && authenticationService.isTeacher() && !addingLesson">
|
||||
<app-error-message [errorTitle]="'You do not have any lessons'" [errorContent]="'You can add one by clicking on the icon above'"
|
||||
[customClass]="'warning'" [closable]="false"></app-error-message>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,111 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { MdDialogRef } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'app-join-session-dialog',
|
||||
template: `
|
||||
<div>
|
||||
<h1 md-dialog-title>
|
||||
Video options
|
||||
</h1>
|
||||
<form #dialogForm (ngSubmit)="joinSession()">
|
||||
<md-dialog-content>
|
||||
<div id="quality-div">
|
||||
<h5>Quality</h5>
|
||||
<md-radio-group [(ngModel)]="quality" name="quality" id="quality">
|
||||
<md-radio-button value='low' title="320x240">Low</md-radio-button>
|
||||
<md-radio-button value='medium' title="640x480">Medium</md-radio-button>
|
||||
<md-radio-button value='high' title="1280x720">High</md-radio-button>
|
||||
<md-radio-button value='veryhigh' title="1920x1080">Very high</md-radio-button>
|
||||
</md-radio-group>
|
||||
</div>
|
||||
<div id="join-div">
|
||||
<h5>Enter with active...</h5>
|
||||
<md-checkbox [(ngModel)]="joinWithVideo" name="joinWithVideo" id="joinWithVideo">Video</md-checkbox>
|
||||
<md-checkbox [(ngModel)]="joinWithAudio" name="joinWithAudio">Audio</md-checkbox>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions>
|
||||
<button md-button md-dialog-close>CANCEL</button>
|
||||
<button md-button id="join-btn" type="submit">JOIN</button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
#quality-div {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#join-div {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#quality-tag {
|
||||
display: block;
|
||||
}
|
||||
h5 {
|
||||
margin-bottom: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
#joinWithVideo {
|
||||
margin-right: 50px;
|
||||
}
|
||||
md-dialog-actions {
|
||||
display: block;
|
||||
}
|
||||
#join-btn {
|
||||
float: right;
|
||||
}
|
||||
`],
|
||||
})
|
||||
export class JoinSessionDialogComponent {
|
||||
|
||||
public myReference: MdDialogRef<JoinSessionDialogComponent>;
|
||||
private quality = 'medium';
|
||||
private joinWithVideo = true;
|
||||
private joinWithAudio = true;
|
||||
|
||||
constructor() { }
|
||||
|
||||
joinSession() {
|
||||
let cameraOptions = {
|
||||
audio: this.joinWithAudio,
|
||||
video: this.joinWithVideo,
|
||||
data: true,
|
||||
mediaConstraints: this.generateMediaConstraints()
|
||||
};
|
||||
this.myReference.close(cameraOptions);
|
||||
}
|
||||
|
||||
generateMediaConstraints() {
|
||||
let mediaConstraints = {
|
||||
audio: true,
|
||||
video: {}
|
||||
}
|
||||
let w = 640;
|
||||
let h = 480;
|
||||
switch (this.quality) {
|
||||
case 'low':
|
||||
w = 320;
|
||||
h = 240;
|
||||
break;
|
||||
case 'medium':
|
||||
w = 640;
|
||||
h = 480;
|
||||
break;
|
||||
case 'high':
|
||||
w = 1280;
|
||||
h = 720;
|
||||
break;
|
||||
case 'veryhigh':
|
||||
w = 1920;
|
||||
h = 1080;
|
||||
break;
|
||||
}
|
||||
mediaConstraints.video['width'] = { exact: w };
|
||||
mediaConstraints.video['height'] = { exact: h };
|
||||
//mediaConstraints.video['frameRate'] = { ideal: Number((<HTMLInputElement>document.getElementById('frameRate')).value) };
|
||||
|
||||
return mediaConstraints;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
.fail {
|
||||
background-color: rgba(167, 56, 65, 0.2);
|
||||
color: #a73841;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: rgba(175, 110, 0, 0.2);
|
||||
color: #af6e00;
|
||||
}
|
||||
|
||||
.correct {
|
||||
background-color: rgba(55, 86, 70, 0.25);
|
||||
color: #375546;
|
||||
}
|
||||
|
||||
md-icon {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
md-card {
|
||||
max-width: 400px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: none;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<md-card [ngClass]="customClass">
|
||||
<md-icon *ngIf="closable" (click)="closeAlert()">clear</md-icon>
|
||||
<md-card-title>{{this.errorTitle}}</md-card-title>
|
||||
<md-card-subtitle [innerHTML]="this.errorContent"></md-card-subtitle>
|
||||
</md-card>
|
|
@ -0,0 +1,30 @@
|
|||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-error-message',
|
||||
templateUrl: './error-message.component.html',
|
||||
styleUrls: ['./error-message.component.css']
|
||||
})
|
||||
export class ErrorMessageComponent {
|
||||
|
||||
@Input()
|
||||
errorTitle: string;
|
||||
@Input()
|
||||
errorContent: string;
|
||||
@Input()
|
||||
customClass: string;
|
||||
@Input()
|
||||
closable: boolean;
|
||||
@Input()
|
||||
timeable: number;
|
||||
|
||||
@Output()
|
||||
eventShowable = new EventEmitter<boolean>();
|
||||
|
||||
constructor() { }
|
||||
|
||||
public closeAlert() {
|
||||
this.eventShowable.emit(false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
.attender-email {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.no-margin-bottom {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.attender-row {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
min-height: 27px;
|
||||
}
|
||||
|
||||
#new-attender-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
/*Rotating animation*/
|
||||
|
||||
@-webkit-keyframes rotating
|
||||
/* Safari and Chrome */
|
||||
|
||||
{
|
||||
from {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
from {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rotating {
|
||||
-webkit-animation: rotating 1s linear infinite;
|
||||
-moz-animation: rotating 1s linear infinite;
|
||||
-ms-animation: rotating 1s linear infinite;
|
||||
-o-animation: rotating 1s linear infinite;
|
||||
animation: rotating 1s linear infinite;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.rotating:hover {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
|
||||
/*End rotating animation*/
|
|
@ -0,0 +1,66 @@
|
|||
<div *ngIf="lesson" fxLayout="row" fxLayoutAlign="center center">
|
||||
<div class="div-inner-main" [style.xs]="{'width': '100%'}">
|
||||
|
||||
<div *ngIf="!editingTitle" fxLayout="row" fxLayoutAlign="center center">
|
||||
<md-icon fxFlex="15%" fxLayoutAlign="start center" (click)="router.navigate(['/lessons'])" [title]="'Back to lessons'">keyboard_arrow_left</md-icon>
|
||||
<h2 fxFlex="70%">{{lesson.title}}</h2>
|
||||
<md-icon fxFlex="15%" fxLayoutAlign="end center" (click)="editingTitle = true; titleEdited = lesson.title" [title]="'Edit lesson'">mode_edit</md-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="editingTitle" fxLayout="row" fxLayoutAlign="start center">
|
||||
<form #editLessonForm (ngSubmit)="editLesson(); editLessonForm.reset()" [class.filtered]="sumbitEditLesson">
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Title" [(ngModel)]="titleEdited" name="lessonTitle" type="text" autocomplete="off" required>
|
||||
</md-input-container>
|
||||
<div class="block-btn">
|
||||
<button md-button type="submit" [disabled]="sumbitEditLesson">Send</button>
|
||||
<a md-button (click)="editingTitle = false; titleEdited = ''" [disabled]="sumbitEditLesson">Cancel</a>
|
||||
<a md-button (click)="deleteLesson()" [disabled]="sumbitEditLesson">Delete lesson</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form #addAttendersForm (ngSubmit)="addLessonAttenders(); addAttendersForm.reset()" [class.filtered]="sumbitAddAttenders">
|
||||
<h4 id="new-attender-title">New attender</h4>
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Email" [(ngModel)]="emailAttender" name="attenderEmail" type="text" autocomplete="off" required>
|
||||
</md-input-container>
|
||||
<div class="block-btn">
|
||||
<button md-button type="submit" [disabled]="sumbitAddAttenders">Send</button>
|
||||
<a md-button (click)="addAttendersForm.reset()" [disabled]="sumbitAddAttenders || emailAttender == null">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<app-error-message *ngIf="addAttendersCorrect" (eventShowable)="addAttendersCorrect = false" [errorTitle]="attCorrectTitle"
|
||||
[errorContent]="attCorrectContent" [customClass]="'correct'" [closable]="true"></app-error-message>
|
||||
<app-error-message *ngIf="addAttendersError" (eventShowable)="addAttendersError = false" [errorTitle]="attErrorTitle" [errorContent]="attErrorContent"
|
||||
[customClass]="'fail'" [closable]="true"></app-error-message>
|
||||
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="20px" fxLayoutAlign="space-between center" fxLayoutAlign.xs="start"
|
||||
class="attender-row">
|
||||
<div fxFlex="90%" class="no-margin-bottom">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="space-between center" fxLayoutAlign.xs="start" fxLayoutGap="20px">
|
||||
<div class="no-margin-bottom" fxFlex>{{authenticationService.getCurrentUser().nickName}}</div>
|
||||
<div class="attender-email" fxFlex>{{authenticationService.getCurrentUser().name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="10%"></div>
|
||||
</div>
|
||||
<div *ngFor="let attender of lesson.attenders; let i = index">
|
||||
<div *ngIf="attender.id != authenticationService.getCurrentUser().id" fxLayout="row" fxLayoutAlign.xs="start" fxLayoutGap="20px"
|
||||
class="attender-row">
|
||||
<div fxFlex="90%">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="space-between center" fxLayoutAlign.xs="start" fxLayoutGap="20px">
|
||||
<div class="no-margin-bottom" fxFlex>{{attender.nickName}}</div>
|
||||
<div class="attender-email" fxFlex>{{attender.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="10%">
|
||||
<md-icon *ngIf="!this.arrayOfAttDels[i]" (click)="deleteLessonAttender(i, attender)" [title]="'Remove attender'">clear</md-icon>
|
||||
<md-icon *ngIf="this.arrayOfAttDels[i]" class="rotating">cached</md-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,186 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
import { MdSnackBar } from '@angular/material';
|
||||
|
||||
import { Lesson } from '../../models/lesson';
|
||||
import { User } from '../../models/user';
|
||||
|
||||
import { LessonService } from '../../services/lesson.service';
|
||||
import { AuthenticationService } from '../../services/authentication.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lesson-details',
|
||||
templateUrl: './lesson-details.component.html',
|
||||
styleUrls: ['./lesson-details.component.css'],
|
||||
})
|
||||
export class LessonDetailsComponent implements OnInit {
|
||||
|
||||
lesson: Lesson;
|
||||
|
||||
editingTitle = false;
|
||||
titleEdited: string;
|
||||
sumbitEditLesson = false;
|
||||
emailAttender: string;
|
||||
sumbitAddAttenders = false;
|
||||
arrayOfAttDels = [];
|
||||
|
||||
// Feedback message parameters
|
||||
addAttendersCorrect = false;
|
||||
addAttendersError = false;
|
||||
attErrorTitle: string;
|
||||
attErrorContent: string;
|
||||
attCorrectTitle: string;
|
||||
attCorrectContent: string;
|
||||
|
||||
constructor(
|
||||
private lessonService: LessonService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private location: Location,
|
||||
public snackBar: MdSnackBar
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params
|
||||
.map((params: Params) => this.lessonService.obtainLocalLesson(+params['id']))
|
||||
.subscribe(lesson => this.lesson = lesson);
|
||||
}
|
||||
|
||||
editLesson() {
|
||||
if (this.titleEdited !== this.lesson.title) {
|
||||
this.sumbitEditLesson = true;
|
||||
let l = new Lesson(this.titleEdited);
|
||||
l.id = this.lesson.id;
|
||||
this.lessonService.editLesson(l).subscribe(
|
||||
lesson => {
|
||||
// Lesson has been updated
|
||||
console.log('Lesson edited: ');
|
||||
console.log(lesson);
|
||||
this.lesson = lesson;
|
||||
this.sumbitEditLesson = false;
|
||||
this.editingTitle = false;
|
||||
this.snackBar.open('Lesson edited!', undefined, { duration: 3000 });
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.sumbitEditLesson = false;
|
||||
this.snackBar.open('There has been a problem...', undefined, { duration: 3000 });
|
||||
});
|
||||
} else {
|
||||
this.editingTitle = false; // The user has not modified the title
|
||||
}
|
||||
}
|
||||
|
||||
deleteLesson() {
|
||||
this.sumbitEditLesson = true;
|
||||
this.lessonService.deleteLesson(this.lesson.id).subscribe(
|
||||
lesson => {
|
||||
// Lesson has been deleted
|
||||
console.log('Lesson deleted');
|
||||
console.log(lesson);
|
||||
this.sumbitEditLesson = false;
|
||||
this.router.navigate(['/lessons']);
|
||||
this.snackBar.open('Lesson deleted!', undefined, { duration: 3000 });
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.sumbitEditLesson = false;
|
||||
this.snackBar.open('There has been a problem...', undefined, { duration: 3000 });
|
||||
});
|
||||
}
|
||||
|
||||
addLessonAttenders() {
|
||||
this.sumbitAddAttenders = true;
|
||||
this.lessonService.addLessonAttenders(this.lesson.id, [this.emailAttender]).subscribe(
|
||||
response => { // response: attendersAdded, attendersAlreadyAdded, emailsInvalid, emailsValidNotRegistered
|
||||
console.log('Attender added');
|
||||
console.log(response);
|
||||
this.sumbitAddAttenders = false;
|
||||
let newAttenders = response.attendersAdded as User[];
|
||||
this.lesson.attenders = this.lesson.attenders.concat(newAttenders);
|
||||
this.handleAttendersMessage(response);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.sumbitAddAttenders = false;
|
||||
this.snackBar.open('There has been a problem...', undefined, { duration: 3000 });
|
||||
});
|
||||
}
|
||||
|
||||
deleteLessonAttender(i: number, attender: User) {
|
||||
this.arrayOfAttDels[i] = true;
|
||||
let l = new Lesson(this.lesson.title);
|
||||
l.id = this.lesson.id;
|
||||
for (let i = 0; i < this.lesson.attenders.length; i++) {
|
||||
if (this.lesson.attenders[i].id !== attender.id) {
|
||||
l.attenders.push(new User(this.lesson.attenders[i])); //Inserting a new User object equal to the attender but "lessons" array empty
|
||||
}
|
||||
}
|
||||
this.lessonService.deleteLessonAttenders(l).subscribe(
|
||||
attenders => {
|
||||
console.log('Attender removed');
|
||||
console.log(attenders);
|
||||
this.arrayOfAttDels[i] = false;
|
||||
this.lesson.attenders = attenders;
|
||||
this.snackBar.open('Attender removed!', undefined, { duration: 3000 });
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.arrayOfAttDels[i] = false;
|
||||
this.snackBar.open('There has been a problem...', undefined, { duration: 3000 });
|
||||
});
|
||||
}
|
||||
|
||||
// Creates an error message when there is any problem during the process of adding Users to a Lesson
|
||||
// It also generates a correct feedback message when any student has been correctly added to the Lesson
|
||||
handleAttendersMessage(response) {
|
||||
let isError: boolean = false;
|
||||
let isCorrect: boolean = false;
|
||||
this.attErrorContent = "";
|
||||
this.attCorrectContent = "";
|
||||
|
||||
if (response.attendersAdded.length > 0) {
|
||||
for (let user of response.attendersAdded) {
|
||||
this.attCorrectContent += "<span class='feedback-list'>" + user.name + "</span>";
|
||||
}
|
||||
isCorrect = true;
|
||||
}
|
||||
if (response.attendersAlreadyAdded.length > 0) {
|
||||
this.attErrorContent += "<span>The following users were already added to the lesson</span>";
|
||||
for (let user of response.attendersAlreadyAdded) {
|
||||
this.attErrorContent += "<span class='feedback-list'>" + user.name + "</span>";
|
||||
}
|
||||
isError = true;
|
||||
}
|
||||
if (response.emailsValidNotRegistered.length > 0) {
|
||||
this.attErrorContent += "<span>The following users are not registered</span>";
|
||||
for (let email of response.emailsValidNotRegistered) {
|
||||
this.attErrorContent += "<span class='feedback-list'>" + email + "</span>";
|
||||
}
|
||||
isError = true;
|
||||
}
|
||||
if (response.emailsInvalid) {
|
||||
if (response.emailsInvalid.length > 0) {
|
||||
this.attErrorContent += "<span>These are not valid emails</span>";
|
||||
for (let email of response.emailsInvalid) {
|
||||
this.attErrorContent += "<span class='feedback-list'>" + email + "</span>";
|
||||
}
|
||||
isError = true;
|
||||
}
|
||||
}
|
||||
if (isError) {
|
||||
this.attErrorTitle = "There have been some problems";
|
||||
this.addAttendersError = true;
|
||||
} else if (response.attendersAdded.length == 0) {
|
||||
this.attErrorTitle = "No emails there!";
|
||||
this.addAttendersError = true;
|
||||
}
|
||||
if (isCorrect) {
|
||||
this.attCorrectTitle = "The following users where properly added";
|
||||
this.addAttendersCorrect = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
md-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
md-card-actions {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
color: #9e9e9e;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card-button {
|
||||
margin-left: 10px !important;
|
||||
}
|
||||
|
||||
.radio-button-div {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#sign-up-as {
|
||||
color: #9e9e9e;
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<div fxLayout="row" fxLayoutAlign="center center">
|
||||
<div class="div-inner-main" [style.xs]="{'width': '100%'}">
|
||||
|
||||
<h1>OpenVidu Demo</h1>
|
||||
|
||||
<div fxLayout="column" fxLayoutAlign="space-around center">
|
||||
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
|
||||
<div *ngIf="submitProcessing" class="cssload-container">
|
||||
<div class="cssload-tube-tunnel"></div>
|
||||
</div>
|
||||
|
||||
<form #myForm (ngSubmit)="onSubmit()" [class.filtered]="submitProcessing">
|
||||
|
||||
<div>
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Email" [(ngModel)]="email" name="email" id="email" type="email" required>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loginView">
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Name" [(ngModel)]="nickName" name="nickName" id="nickName" type="text" autocomplete="off" required>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Password" [(ngModel)]="password" name="password" id="password" type="password" required>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loginView">
|
||||
<md-input-container>
|
||||
<input mdInput placeholder="Confirm password" [(ngModel)]="confirmPassword" name="confirmPassword" id="confirmPassword" type="password"
|
||||
autocomplete="off" required>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loginView" class="radio-button-div">
|
||||
<span id="sign-up-as">Sign up as...</span>
|
||||
<md-radio-group [(ngModel)]="roleUserSignup" name="roleUserSignup" id="roleUserSignup">
|
||||
<md-radio-button value='student'>Student</md-radio-button>
|
||||
<md-radio-button value='teacher'>Teacher</md-radio-button>
|
||||
</md-radio-group>
|
||||
</div>
|
||||
|
||||
<app-error-message *ngIf="fieldsIncorrect" (eventShowable)="fieldsIncorrect = false" [errorTitle]="errorTitle" [errorContent]="errorContent"
|
||||
[customClass]="customClass" [closable]="true"></app-error-message>
|
||||
|
||||
<div class="btn-container">
|
||||
<button md-raised-button color="accent" type="submit" *ngIf="loginView" id="log-in-btn">Log in</button>
|
||||
<button md-raised-button color="primary" type="submit" *ngIf="!loginView" id="sign-up-btn">Sign up</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</md-card-content>
|
||||
|
||||
<md-card-actions>
|
||||
<div *ngIf="loginView">Not registered yet?<button md-button (click)="setLoginView(false); myForm.reset()" class="card-button">Sign up</button></div>
|
||||
<div *ngIf="!loginView">Already registered?<button md-button (click)="setLoginView(true); myForm.reset()" class="card-button">Log in</button></div>
|
||||
</md-card-actions>
|
||||
|
||||
</md-card>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,134 @@
|
|||
import { Component, OnInit, AfterViewChecked } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthenticationService } from '../../services/authentication.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-presentation',
|
||||
templateUrl: './presentation.component.html',
|
||||
styleUrls: ['./presentation.component.css']
|
||||
})
|
||||
|
||||
export class PresentationComponent implements OnInit, AfterViewChecked {
|
||||
|
||||
private email: string;
|
||||
private password: string;
|
||||
private confirmPassword: string;
|
||||
private nickName: string;
|
||||
private roleUserSignup = 'student';
|
||||
|
||||
private loginView = true;
|
||||
private fieldsIncorrect: boolean;
|
||||
private submitProcessing: boolean;
|
||||
|
||||
// Error message content
|
||||
private errorTitle: string;
|
||||
private errorContent: string;
|
||||
private customClass: string;
|
||||
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private userService: UserService,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
// If the user is loggedIn, navigates to dashboard
|
||||
ngAfterViewChecked() {
|
||||
if (this.authenticationService.isLoggedIn()) {
|
||||
this.router.navigate(['/lessons']);
|
||||
}
|
||||
}
|
||||
|
||||
setLoginView(option: boolean) {
|
||||
this.fieldsIncorrect = false;
|
||||
this.loginView = option;
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
console.log('Submit: email = ' + this.email + ' , password = ' + this.password + ', confirmPassword = ' + this.confirmPassword);
|
||||
this.submitProcessing = true;
|
||||
|
||||
if (this.loginView) {
|
||||
// If login view is activated
|
||||
console.log('Logging in...');
|
||||
this.logIn(this.email, this.password);
|
||||
} else {
|
||||
// If signup view is activated
|
||||
console.log('Signing up...');
|
||||
this.signUp();
|
||||
}
|
||||
}
|
||||
|
||||
logIn(user: string, pass: string) {
|
||||
this.authenticationService.logIn(user, pass).subscribe(
|
||||
result => {
|
||||
this.submitProcessing = false;
|
||||
|
||||
console.log('Login succesful! LOGGED AS ' + this.authenticationService.getCurrentUser().name);
|
||||
|
||||
// Login successful
|
||||
this.fieldsIncorrect = false;
|
||||
this.router.navigate(['/lessons']);
|
||||
},
|
||||
error => {
|
||||
|
||||
console.log('Login failed (error): ' + error);
|
||||
|
||||
this.errorTitle = 'Invalid field';
|
||||
this.errorContent = 'Please check your email or password';
|
||||
this.customClass = 'fail';
|
||||
|
||||
// Login failed
|
||||
this.handleError();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
signUp() {
|
||||
|
||||
// Passwords don't match
|
||||
if (this.password !== this.confirmPassword) {
|
||||
this.errorTitle = 'Your passwords don\'t match!';
|
||||
this.errorContent = '';
|
||||
this.customClass = 'fail';
|
||||
this.handleError();
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
let userNameFixed = this.email;
|
||||
let userPasswordFixed = this.password;
|
||||
|
||||
this.userService.newUser(userNameFixed, userPasswordFixed, this.nickName, this.roleUserSignup).subscribe(
|
||||
result => {
|
||||
|
||||
// Sign up succesful
|
||||
this.logIn(userNameFixed, userPasswordFixed);
|
||||
console.log('Sign up succesful!');
|
||||
},
|
||||
error => {
|
||||
|
||||
console.log('Sign up failed (error): ' + error);
|
||||
if (error === 409) { // CONFLICT: Email already in use
|
||||
this.errorTitle = 'Invalid email';
|
||||
this.errorContent = 'That email is already in use';
|
||||
this.customClass = 'fail';
|
||||
} else if (error === 403) { // FORBIDDEN: Invalid email format
|
||||
this.errorTitle = 'Invalid email format';
|
||||
this.errorContent = 'Our server has rejected that email';
|
||||
this.customClass = 'fail';
|
||||
}
|
||||
|
||||
// Sign up failed
|
||||
this.handleError();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleError() {
|
||||
this.submitProcessing = false;
|
||||
this.fieldsIncorrect = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
table {
|
||||
margin-top: 15px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 15px 17px;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<div fxLayout="row" fxLayoutAlign="center center">
|
||||
<div class="div-inner-main" [style.xs]="{'width': '100%'}">
|
||||
|
||||
<div>MY PROFILE</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<th>{{authenticationService.getCurrentUser().nickName}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Email</td>
|
||||
<th>{{authenticationService.getCurrentUser().name}}</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { AuthenticationService } from '../../services/authentication.service';
|
||||
import { User } from '../../models/user';
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.css']
|
||||
})
|
||||
export class ProfileComponent implements OnInit {
|
||||
|
||||
private user: User;
|
||||
|
||||
constructor(private authenticationService: AuthenticationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.user = this.authenticationService.getCurrentUser();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#header-div {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
md-icon {
|
||||
font-size: 38px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
color: white;
|
||||
-webkit-transition: color .2s linear;
|
||||
-moz-transition: color .2s linear;
|
||||
-ms-transition: color .2s linear;
|
||||
-o-transition: color .2s linear;
|
||||
transition: color .2s linear;
|
||||
}
|
||||
|
||||
md-icon:hover {
|
||||
color: #ffd740;
|
||||
}
|
||||
|
||||
#back-btn {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right-btn {
|
||||
float: right;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<div id="header-div">
|
||||
<md-icon id="back-btn" (click)="location.back()" [title]="'Back to lessons'">keyboard_arrow_left</md-icon>
|
||||
<md-icon class="right-btn" (click)="toggleFullScreen()" [title]="'Fullscreen'">{{fullscreenIcon}}</md-icon>
|
||||
<md-icon class="right-btn" (click)="toggleLocalVideo()" [title]="'Toggle video'">{{videoIcon}}</md-icon>
|
||||
<md-icon class="right-btn" (click)="toggleLocalAudio()" [title]="'Toggle audio'">{{audioIcon}}</md-icon>
|
||||
<h1>{{lesson?.title}}</h1>
|
||||
</div>
|
||||
<div id="local-stream"></div>
|
||||
<div id="remote-streams"></div>
|
|
@ -0,0 +1,252 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { OpenVidu, Session, Stream } from 'openvidu-browser';
|
||||
|
||||
import { VideoSessionService } from '../../services/video-session.service';
|
||||
import { AuthenticationService } from '../../services/authentication.service';
|
||||
|
||||
import { Lesson } from '../../models/lesson';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-session',
|
||||
templateUrl: './video-session.component.html',
|
||||
styleUrls: ['./video-session.component.css']
|
||||
})
|
||||
export class VideoSessionComponent implements OnInit {
|
||||
|
||||
lesson: Lesson;
|
||||
|
||||
OV: OpenVidu;
|
||||
session: Session;
|
||||
|
||||
sessionId: string;
|
||||
token: string;
|
||||
|
||||
cameraOptions: any;
|
||||
localParentId: string = 'local-stream';
|
||||
remoteParentId: string = 'remote-streams';
|
||||
|
||||
localVideoActivated: boolean;
|
||||
localAudioActivated: boolean;
|
||||
videoIcon: string;
|
||||
audioIcon: string;
|
||||
fullscreenIcon: string;
|
||||
|
||||
constructor(
|
||||
private location: Location,
|
||||
private authenticationService: AuthenticationService,
|
||||
private videoSessionService: VideoSessionService) { }
|
||||
|
||||
|
||||
OPEN_VIDU_CONNECTION() {
|
||||
|
||||
// 0) Obtain 'sessionId' and 'token' from server
|
||||
// In this case, the method ngOnInit takes care of it
|
||||
|
||||
// 1) Initialize OpenVidu and your Session
|
||||
this.OV = new OpenVidu("wss://" + location.hostname + ":8443/");
|
||||
this.session = this.OV.initSession(this.sessionId);
|
||||
|
||||
|
||||
// 2) Specify the actions when participants enter and leave the session
|
||||
this.session.onStreamAddedOV((stream) => {
|
||||
console.warn("Stream added:");
|
||||
console.warn(stream);
|
||||
stream.playOnlyVideo(this.remoteParentId, null);
|
||||
});
|
||||
this.session.onStreamRemovedOV((stream) => {
|
||||
console.warn("Stream removed:");
|
||||
console.warn(stream);
|
||||
stream.removeVideo(this.remoteParentId);
|
||||
});
|
||||
this.session.onParticipantJoinedOV((participant) => {
|
||||
console.warn("Participant joined:");
|
||||
console.warn(participant);
|
||||
});
|
||||
this.session.onParticipantLeftOV((participant) => {
|
||||
console.warn("Participant left:");
|
||||
console.warn(participant);
|
||||
});
|
||||
this.session.onParticipantPublishedOV((participant) => {
|
||||
console.warn("Participant published:");
|
||||
console.warn(participant);
|
||||
});
|
||||
this.session.onParticipantEvictedOV((participant) => {
|
||||
console.warn("Participant evicted:");
|
||||
console.warn(participant);
|
||||
});
|
||||
this.session.onRoomClosedOV((room) => {
|
||||
console.warn("Room closed:");
|
||||
console.warn(room);
|
||||
});
|
||||
this.session.onLostConnectionOV((room) => {
|
||||
console.warn("Connection lost:");
|
||||
console.warn(room);
|
||||
});
|
||||
this.session.onMediaErrorOV((error) => {
|
||||
console.warn("Media error:");
|
||||
console.warn(error);
|
||||
});
|
||||
|
||||
|
||||
// 3) Connect to the session
|
||||
this.session.connect(this.token, (error) => {
|
||||
if (error) return console.log("There was an error: " + error);
|
||||
|
||||
|
||||
// 4) Get your own camera stream with the desired resolution and publish it, only if the user is supposed to do so
|
||||
|
||||
// Local publish generating an HTML video element as a child of parentId HTML element
|
||||
this.OV.initPublisherTagged(this.localParentId, this.cameraOptions, (error) => {
|
||||
if (error) return console.log("There was an error with your camera: " + error);
|
||||
this.session.publish();
|
||||
});
|
||||
|
||||
// Local publish without generating an HTML video element
|
||||
/*this.OV.initPublisher(this.cameraOptions, (error) => {
|
||||
if (error) return console.log("There was an error: " + error);
|
||||
this.session.publish();
|
||||
});*/
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
// Specific aspects of this concrete application
|
||||
this.previousConnectionStuff();
|
||||
|
||||
|
||||
if (this.authenticationService.isTeacher()) {
|
||||
|
||||
// If the user is the teacher: creates the session and gets a token (with PUBLISHER role)
|
||||
this.videoSessionService.createSession(this.lesson.id).subscribe(
|
||||
sessionId => {
|
||||
this.sessionId = sessionId;
|
||||
this.videoSessionService.generateToken(this.lesson.id).subscribe(
|
||||
sessionIdAndToken => {
|
||||
this.token = sessionIdAndToken[1];
|
||||
console.warn("Token: " + this.token);
|
||||
console.warn("SessionId: " + this.sessionId);
|
||||
this.OPEN_VIDU_CONNECTION();
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
||||
// If the user is a student: gets a token (with SUBSCRIBER role)
|
||||
this.videoSessionService.generateToken(this.lesson.id).subscribe(
|
||||
sessionIdAndToken => {
|
||||
this.sessionId = sessionIdAndToken[0];
|
||||
this.token = sessionIdAndToken[1];
|
||||
console.warn("Token: " + this.token);
|
||||
console.warn("SessionId: " + this.sessionId);
|
||||
this.OPEN_VIDU_CONNECTION();
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Specific aspects of this concrete application
|
||||
this.afterConnectionStuff();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.toggleScrollPage("hidden");
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.toggleScrollPage("auto");
|
||||
this.exitFullScreen();
|
||||
if (this.OV) this.OV.close(false);
|
||||
}
|
||||
|
||||
toggleLocalVideo() {
|
||||
this.localVideoActivated = !this.localVideoActivated;
|
||||
this.OV.toggleLocalVideoTrack(this.localVideoActivated);
|
||||
this.videoIcon = this.localVideoActivated ? 'videocam' : 'videocam_off';
|
||||
}
|
||||
|
||||
toggleLocalAudio() {
|
||||
this.localAudioActivated = !this.localAudioActivated;
|
||||
this.OV.toggleLocalAudioTrack(this.localAudioActivated);
|
||||
this.audioIcon = this.localAudioActivated ? 'mic' : 'mic_off';
|
||||
}
|
||||
|
||||
toggleScrollPage(scroll: string) {
|
||||
let content = <HTMLElement>document.getElementsByClassName("mat-sidenav-content")[0];
|
||||
content.style.overflow = scroll;
|
||||
}
|
||||
|
||||
toggleFullScreen() {
|
||||
let document: any = window.document;
|
||||
let fs = document.getElementsByTagName('html')[0];
|
||||
if (!document.fullscreenElement &&
|
||||
!document.mozFullScreenElement &&
|
||||
!document.webkitFullscreenElement &&
|
||||
!document.msFullscreenElement) {
|
||||
console.log("enter FULLSCREEN!");
|
||||
this.fullscreenIcon = 'fullscreen_exit';
|
||||
if (fs.requestFullscreen) {
|
||||
fs.requestFullscreen();
|
||||
} else if (fs.msRequestFullscreen) {
|
||||
fs.msRequestFullscreen();
|
||||
} else if (fs.mozRequestFullScreen) {
|
||||
fs.mozRequestFullScreen();
|
||||
} else if (fs.webkitRequestFullscreen) {
|
||||
fs.webkitRequestFullscreen();
|
||||
}
|
||||
} else {
|
||||
console.log("exit FULLSCREEN!");
|
||||
this.fullscreenIcon = 'fullscreen';
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exitFullScreen() {
|
||||
let document: any = window.document;
|
||||
let fs = document.getElementsByTagName('html')[0];
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
previousConnectionStuff() {
|
||||
this.lesson = this.videoSessionService.lesson;
|
||||
this.cameraOptions = this.videoSessionService.cameraOptions;
|
||||
}
|
||||
|
||||
afterConnectionStuff() {
|
||||
this.localVideoActivated = this.cameraOptions.video;
|
||||
this.localAudioActivated = this.cameraOptions.audio;
|
||||
this.videoIcon = this.localVideoActivated ? "videocam" : "videocam_off";
|
||||
this.audioIcon = this.localAudioActivated ? "mic" : "mic_off";
|
||||
this.fullscreenIcon = "fullscreen";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { User } from './user';
|
||||
|
||||
export class Lesson {
|
||||
|
||||
public id?: number;
|
||||
public title: string;
|
||||
public teacher: User;
|
||||
public attenders: User[];
|
||||
|
||||
constructor(title: string) {
|
||||
this.title = title;
|
||||
this.attenders = [];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { Lesson } from './lesson';
|
||||
|
||||
export class User {
|
||||
|
||||
public id?: number;
|
||||
public name: string;
|
||||
public nickName: string;
|
||||
public roles: string[];
|
||||
public lessons: Lesson[];
|
||||
|
||||
constructor(u: User) {
|
||||
this.id = u.id;
|
||||
this.name = u.name;
|
||||
this.nickName = u.nickName;
|
||||
this.roles = u.roles;
|
||||
this.lessons = [];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
import { Observable } from 'rxjs/Rx';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, RequestOptions, Headers, Response } from '@angular/http';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { User } from '../models/user';
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationService {
|
||||
|
||||
private urlLogIn = environment.URL_BACK + '/api-logIn';
|
||||
private urlLogOut = environment.URL_BACK + '/api-logOut';
|
||||
|
||||
public token: string;
|
||||
private user: User;
|
||||
private role: string;
|
||||
|
||||
constructor(private http: Http, private router: Router) {
|
||||
this.reqIsLogged();
|
||||
|
||||
// set token if saved in local storage
|
||||
// let auth_token = JSON.parse(localStorage.getItem('auth_token'));
|
||||
// this.token = auth_token && auth_token.token;
|
||||
}
|
||||
|
||||
logIn(user: string, pass: string) {
|
||||
|
||||
console.log('Login service started...');
|
||||
|
||||
let userPass = utf8_to_b64(user + ':' + pass);
|
||||
let headers = new Headers({
|
||||
'Authorization': 'Basic ' + userPass,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
});
|
||||
let options = new RequestOptions({ headers });
|
||||
|
||||
return this.http.get(this.urlLogIn, options)
|
||||
.map(response => {
|
||||
this.processLogInResponse(response);
|
||||
return this.user;
|
||||
})
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
logOut() {
|
||||
|
||||
console.log('Logging out...');
|
||||
|
||||
return this.http.get(this.urlLogOut).map(
|
||||
response => {
|
||||
|
||||
console.log('Logout succesful!');
|
||||
|
||||
this.user = null;
|
||||
this.role = null;
|
||||
|
||||
// clear token remove user from local storage to log user out and navigates to welcome page
|
||||
this.token = null;
|
||||
localStorage.removeItem('login');
|
||||
localStorage.removeItem('rol');
|
||||
this.router.navigate(['']);
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
directLogOut() {
|
||||
this.logOut().subscribe(
|
||||
response => { },
|
||||
error => console.log("Error when trying to log out: " + error)
|
||||
);
|
||||
}
|
||||
|
||||
private processLogInResponse(response) {
|
||||
// Correctly logged in
|
||||
console.log('Login succesful processing...');
|
||||
|
||||
this.user = (response.json() as User);
|
||||
|
||||
localStorage.setItem('login', 'OPENVIDUAPP');
|
||||
if (this.user.roles.indexOf('ROLE_TEACHER') !== -1) {
|
||||
this.role = 'ROLE_TEACHER';
|
||||
localStorage.setItem('rol', 'ROLE_TEACHER');
|
||||
}
|
||||
if (this.user.roles.indexOf('ROLE_STUDENT') !== -1) {
|
||||
this.role = 'ROLE_STUDENT';
|
||||
localStorage.setItem('rol', 'ROLE_STUDENT');
|
||||
}
|
||||
}
|
||||
|
||||
reqIsLogged() {
|
||||
|
||||
console.log('ReqIsLogged called');
|
||||
|
||||
let headers = new Headers({
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
});
|
||||
let options = new RequestOptions({ headers });
|
||||
|
||||
this.http.get(this.urlLogIn, options).subscribe(
|
||||
response => this.processLogInResponse(response),
|
||||
error => {
|
||||
if (error.status != 401) {
|
||||
console.error('Error when asking if logged: ' + JSON.stringify(error));
|
||||
this.logOut();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
checkCredentials() {
|
||||
if (!this.isLoggedIn()) {
|
||||
this.logOut();
|
||||
}
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
return ((this.user != null) && (this.user !== undefined));
|
||||
}
|
||||
|
||||
getCurrentUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
isTeacher() {
|
||||
return ((this.user.roles.indexOf('ROLE_TEACHER')) !== -1) && (localStorage.getItem('rol') === 'ROLE_TEACHER');
|
||||
}
|
||||
|
||||
isStudent() {
|
||||
return ((this.user.roles.indexOf('ROLE_STUDENT')) !== -1) && (localStorage.getItem('rol') === 'ROLE_STUDENT');
|
||||
}
|
||||
|
||||
updateUserLessons(lessons) {
|
||||
this.getCurrentUser().lessons = lessons;
|
||||
}
|
||||
}
|
||||
|
||||
function utf8_to_b64(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
return String.fromCharCode(<any>'0x' + p1);
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Http, Headers, RequestOptions, Response } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
import { Lesson } from '../models/lesson';
|
||||
import { User } from '../models/user';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
|
||||
import 'rxjs/Rx';
|
||||
|
||||
@Injectable()
|
||||
export class LessonService {
|
||||
|
||||
private url = environment.URL_BACK + '/api-lessons';
|
||||
|
||||
constructor(private http: Http, private authenticationService: AuthenticationService) { }
|
||||
|
||||
getLessons(user: User) {
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.get(this.url + '/user/' + user.id, options) // Must send userId
|
||||
.map((response: Response) => response.json() as Lesson[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
getLesson(lessonId: number) {
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.get(this.url + '/lesson/' + lessonId, options) // Must send userId
|
||||
.map((response: Response) => response.json() as Lesson)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// POST new lesson. On success returns the created lesson
|
||||
newLesson(lesson: Lesson) {
|
||||
let body = JSON.stringify(lesson);
|
||||
let headers = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
});
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.post(this.url + '/new', body, options)
|
||||
.map(response => response.json() as Lesson)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// PUT existing lesson. On success returns the updated lesson
|
||||
editLesson(lesson: Lesson) {
|
||||
let body = JSON.stringify(lesson);
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.put(this.url + '/edit', body, options)
|
||||
.map(response => response.json() as Lesson)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// DELETE existing lesson. On success returns the deleted lesson (simplified version)
|
||||
deleteLesson(lessonId: number) {
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.delete(this.url + '/delete/' + lessonId, options)
|
||||
.map(response => response.json() as Lesson)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// PUT existing lesson, modifying its attenders (adding them). On success returns the updated lesson.attenders array
|
||||
addLessonAttenders(lessonId: number, userEmails: string[]) {
|
||||
let body = JSON.stringify(userEmails);
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.put(this.url + '/edit/add-attenders/lesson/' + lessonId, body, options)
|
||||
.map(response => response.json())
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// PUT existing lesson, modifying its attenders (deleting them). On success returns the updated lesson.attenders array
|
||||
deleteLessonAttenders(lesson: Lesson) {
|
||||
let body = JSON.stringify(lesson);
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.put(this.url + '/edit/delete-attenders', body, options)
|
||||
.map(response => response.json() as User[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
obtainLocalLesson(id: number) {
|
||||
return this.authenticationService.getCurrentUser().lessons.find(lesson => lesson.id == id);
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
console.error(error);
|
||||
return Observable.throw('Server error (' + error.status + '): ' + error.text())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Http, Headers, RequestOptions, Response } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { User } from '../models/user';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
|
||||
private url = '/api-users';
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
newUser(name: string, pass: string, nickName: string, role: string) {
|
||||
let body = JSON.stringify([name, pass, nickName, role]);
|
||||
let headers = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
});
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.post(this.url + "/new", body, options)
|
||||
.map(response => response.json() as User)
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
return Observable.throw(error.status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Http, Headers, RequestOptions } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Lesson } from '../models/lesson';
|
||||
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
|
||||
@Injectable()
|
||||
export class VideoSessionService {
|
||||
|
||||
lesson: Lesson;
|
||||
cameraOptions: any;
|
||||
|
||||
constructor(private http: Http, private authenticationService: AuthenticationService) { }
|
||||
|
||||
// Returns "sessionId"
|
||||
createSession(lessonId: number) {
|
||||
let body = JSON.stringify(lessonId);
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.post('/api-sessions/create-session', body, options)
|
||||
.map(response => response.text())
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
// Returns {0: sessionId, 1: token}
|
||||
generateToken(lessonId: number) {
|
||||
let body = JSON.stringify(lessonId);
|
||||
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.authenticationService.token });
|
||||
let options = new RequestOptions({ headers });
|
||||
return this.http.post('/api-sessions/generate-token', body, options)
|
||||
.map(response => response.json())
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
console.error(error);
|
||||
return Observable.throw('Server error (' + error.status + '): ' + error.text())
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
// The file contents for the current environment will overwrite these during build.
|
||||
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
|
||||
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
|
||||
// The list of which env maps to which file can be found in `.angular-cli.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
URL_BACK: 'http://localhost:5000'
|
||||
};
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,23 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OpenviduSampleApp</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Exo+2" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root>
|
||||
<div class="cssload-container">
|
||||
<div class="cssload-tube-tunnel"></div>
|
||||
</div>
|
||||
</app-root>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
import 'core-js/es6/reflect';
|
||||
import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date, currency, decimal and percent pipes.
|
||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
|
@ -0,0 +1,230 @@
|
|||
@import '~@angular/material/core/theming/prebuilt/deeppurple-amber.css';
|
||||
|
||||
* {
|
||||
font-family: 'Exo 2', sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
md-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
md-icon:hover {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.mat-sidenav-content {
|
||||
min-height: 100% !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.hover-link:hover {
|
||||
color: #ffd740 !important;
|
||||
}
|
||||
|
||||
.feedback-list {
|
||||
font-weight: 500;
|
||||
margin-top: 5px;
|
||||
margin-left: 2em;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
.div-inner-main {
|
||||
min-width: 30%;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 50px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.block-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#local-stream {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#local-stream video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#remote-streams {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#remote-streams video {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
|
||||
/* Colors of Deep Purple Material Theme */
|
||||
|
||||
.back-primary {
|
||||
background: #673ab7 !important;
|
||||
}
|
||||
|
||||
.back-accent {
|
||||
background: #ffd740;
|
||||
}
|
||||
|
||||
.back-warn {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.back-secondary {
|
||||
background: #D1C4E9;
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: #673ab7 !important;
|
||||
}
|
||||
|
||||
.color-accent {
|
||||
color: #ffd740;
|
||||
}
|
||||
|
||||
.color-warn {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
color: #D1C4E9;
|
||||
}
|
||||
|
||||
|
||||
/*Loader*/
|
||||
|
||||
.cssload-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 1000;
|
||||
margin: -25px 0 0 -25px;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.cssload-tube-tunnel {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto;
|
||||
border: 5px solid;
|
||||
border-radius: 50%;
|
||||
border-color: #673ab7;
|
||||
animation: cssload-scale 1035ms infinite linear;
|
||||
-o-animation: cssload-scale 1035ms infinite linear;
|
||||
-ms-animation: cssload-scale 1035ms infinite linear;
|
||||
-webkit-animation: cssload-scale 1035ms infinite linear;
|
||||
-moz-animation: cssload-scale 1035ms infinite linear;
|
||||
}
|
||||
|
||||
@keyframes cssload-scale {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
90% {
|
||||
transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes cssload-scale {
|
||||
0% {
|
||||
-o-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
90% {
|
||||
-o-transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
100% {
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes cssload-scale {
|
||||
0% {
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
90% {
|
||||
-ms-transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes cssload-scale {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
90% {
|
||||
-webkit-transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes cssload-scale {
|
||||
0% {
|
||||
-moz-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
90% {
|
||||
-moz-transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
100% {
|
||||
-moz-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.filtered {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
|
||||
/*End Loader*/
|
|
@ -0,0 +1,32 @@
|
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/long-stack-trace-zone';
|
||||
import 'zone.js/dist/proxy.js';
|
||||
import 'zone.js/dist/sync-test';
|
||||
import 'zone.js/dist/jasmine-patch';
|
||||
import 'zone.js/dist/async-test';
|
||||
import 'zone.js/dist/fake-async-test';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
||||
declare var __karma__: any;
|
||||
declare var require: any;
|
||||
|
||||
// Prevent Karma from running prematurely.
|
||||
__karma__.loaded = function () {};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
// Finally, start Karma to run the tests.
|
||||
__karma__.start();
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
],
|
||||
"outDir": "../out-tsc/app",
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"baseUrl": "",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"outDir": "../out-tsc/spec",
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"baseUrl": "",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/* SystemJS module definition */
|
||||
declare var module: {
|
||||
id: string;
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"rulesDirectory": [
|
||||
"node_modules/codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"callable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"import-blacklist": [true, "rxjs"],
|
||||
"import-spacing": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"interface-over-type-literal": true,
|
||||
"label-position": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
"static-before-instance",
|
||||
"variables-before-functions"
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-empty-interface": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": [true, "ignore-params"],
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-string-throw": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"typeof-compare": true,
|
||||
"unified-signatures": true,
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
|
||||
"directive-selector": [true, "attribute", "app", "camelCase"],
|
||||
"component-selector": [true, "element", "app", "kebab-case"],
|
||||
"use-input-property-decorator": true,
|
||||
"use-output-property-decorator": true,
|
||||
"use-host-property-decorator": true,
|
||||
"no-input-rename": true,
|
||||
"no-output-rename": true,
|
||||
"use-life-cycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"component-class-suffix": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-access-missing-member": true,
|
||||
"templates-use-public": true,
|
||||
"invoke-injectable": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue