Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
701282099a | |||
2bc6abf42c | |||
46f911c075 | |||
e362cb1251 | |||
1a52af880d | |||
21b0e64130 | |||
a1541dd51b | |||
67167906aa | |||
ea5643909d | |||
5d1d2ee98a | |||
fa766cb6f1 | |||
634bcffa9d | |||
aafa4a4a11 | |||
891294f267 | |||
48ddfbbbd0 | |||
5c4b976417 | |||
7a5dc1af4d | |||
7adf048438 | |||
d322d09604 | |||
ca2bb89cee | |||
3df853734c | |||
a35b0881b3 | |||
f06f7f0243 | |||
1fc29e9916 | |||
fe563e32ee | |||
cde2a37afe |
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,10 +1,20 @@
|
|||||||
|
# Project specific files
|
||||||
|
admins.conf
|
||||||
|
clients.conf
|
||||||
|
*list.conf
|
||||||
|
config.properties
|
||||||
|
/*.db
|
||||||
|
/application.properties
|
||||||
|
|
||||||
# Created by https://www.gitignore.io
|
# Created by https://www.gitignore.io
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
*.log.*.gz
|
||||||
|
|
||||||
### Gradle ###
|
### Gradle ###
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
classes/
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
# Ignore Gradle GUI config
|
||||||
gradle-app.setting
|
gradle-app.setting
|
||||||
@ -65,7 +75,6 @@ crashlytics-build.properties
|
|||||||
### Eclipse ###
|
### Eclipse ###
|
||||||
*.pydevproject
|
*.pydevproject
|
||||||
.metadata
|
.metadata
|
||||||
.gradle
|
|
||||||
bin/
|
bin/
|
||||||
tmp/
|
tmp/
|
||||||
*.tmp
|
*.tmp
|
||||||
|
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
language: java
|
||||||
|
jdk:
|
||||||
|
- oraclejdk8
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -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.
|
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Jabit Server
|
||||||
|
============
|
||||||
|
|
||||||
|
This is the server node using the Jabit library. You can run it by calling
|
||||||
|
```
|
||||||
|
java -jar jabit-server.jar
|
||||||
|
```
|
||||||
|
The interface will be available on port 9000, Bitmessage as usual on Port 8444.
|
||||||
|
|
||||||
|
There are still a few problems with the interface (the idea is to allow collecting
|
||||||
|
and displaying broadcasts).
|
||||||
|
|
||||||
|
On first startup it will create a config file (allowing you to configure the
|
||||||
|
Bitmessage port), a whitelist, a blacklist and a shortlist. If the whitelist isn't
|
||||||
|
empty, the blacklist will be irrelevant. You can disable the feature by simply
|
||||||
|
adding a valid Bitmessage address to the whitelist. For shortlisted addresses, only
|
||||||
|
the last five broadcasts are displayed and stored (useful e.g. for time services or
|
||||||
|
Q's Aktivlist).
|
||||||
|
|
||||||
|
Building / Development
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
You can build the jar file with
|
||||||
|
```
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
As there is a problem with the build order, you'll need to do this twice.
|
||||||
|
|
||||||
|
To deploy on a Ubuntu server (might work on other Linuxes as well), create a file
|
||||||
|
`/etc/init/jabit.conf` with the following contents:
|
||||||
|
```
|
||||||
|
chdir /srv/jabit
|
||||||
|
|
||||||
|
exec su -s /bin/sh -c 'exec "$0" "$@"' jabit -- /usr/bin/java -jar jabit-server.jar --server.port=9000 > /dev/null
|
||||||
|
|
||||||
|
start on runlevel [2345]
|
||||||
|
stop on runlevel [^2345]
|
||||||
|
|
||||||
|
```
|
||||||
|
there must be a user jabit and a folder `/srv/jabit` where this user has write
|
||||||
|
permission containing `jabit-server.jar`.
|
84
build.gradle
84
build.gradle
@ -1,34 +1,41 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = '1.2.6.RELEASE'
|
kotlin_version = '1.2.41'
|
||||||
|
springBootVersion = '2.0.0.M7'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
|
||||||
classpath("io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE")
|
classpath('se.transmode.gradle:gradle-docker:1.2')
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
|
||||||
|
classpath('com.github.ben-manes:gradle-versions-plugin:0.17.0')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'java'
|
group = 'dissem'
|
||||||
apply plugin: 'eclipse-wtp'
|
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'kotlin-spring'
|
||||||
|
apply plugin: 'eclipse'
|
||||||
apply plugin: 'idea'
|
apply plugin: 'idea'
|
||||||
apply plugin: 'spring-boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
apply plugin: 'war'
|
apply plugin: 'docker'
|
||||||
|
apply plugin: 'com.github.ben-manes.versions'
|
||||||
|
|
||||||
|
|
||||||
war {
|
|
||||||
baseName = 'server'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
}
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
targetCompatibility = 1.8
|
targetCompatibility = 1.8
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url "https://repo.spring.io/snapshot" }
|
||||||
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,20 +43,31 @@ configurations {
|
|||||||
providedRuntime
|
providedRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext.jabitVersion = 'feature-refactoring-SNAPSHOT'
|
||||||
dependencies {
|
dependencies {
|
||||||
compile("org.springframework.boot:spring-boot-starter-hateoas")
|
compile("org.springframework.boot:spring-boot-starter-hateoas")
|
||||||
compile("org.springframework.boot:spring-boot-starter-jersey")
|
compile("org.springframework.boot:spring-boot-starter-jersey")
|
||||||
compile("org.springframework.boot:spring-boot-starter-web")
|
compile("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
compile("io.springfox:springfox-swagger2:2.7.0")
|
||||||
|
compile("io.springfox:springfox-swagger-ui:2.7.0")
|
||||||
|
compile("com.fasterxml.jackson.module:jackson-module-kotlin:")
|
||||||
|
|
||||||
compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT'
|
compile("ch.dissem.jabit:jabit-core:$jabitVersion")
|
||||||
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
|
compile("ch.dissem.jabit:jabit-networking:$jabitVersion")
|
||||||
compile 'ch.dissem.jabit:jabit-repositories:0.2.1-SNAPSHOT'
|
compile("ch.dissem.jabit:jabit-repositories:$jabitVersion")
|
||||||
compile 'ch.dissem.jabit:jabit-security-bouncy:0.2.1-SNAPSHOT'
|
compile("ch.dissem.jabit:jabit-cryptography-bouncy:$jabitVersion")
|
||||||
|
compile("ch.dissem.jabit:jabit-extensions:$jabitVersion")
|
||||||
|
|
||||||
compile 'com.h2database:h2:1.4.187'
|
compile('com.h2database:h2:1.4.194')
|
||||||
|
|
||||||
|
compile('com.google.zxing:core:3.3.1')
|
||||||
|
|
||||||
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
|
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
|
||||||
testCompile("org.springframework.boot:spring-boot-starter-test")
|
testCompile("org.springframework.boot:spring-boot-starter-test")
|
||||||
|
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
|
||||||
|
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version")
|
||||||
|
compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
|
||||||
|
compile("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -60,6 +78,38 @@ eclipse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task buildDocker(type: Docker, dependsOn: build) {
|
||||||
|
push = true
|
||||||
|
applicationName = 'jabit-server'
|
||||||
|
dockerfile = file('src/main/docker/Dockerfile')
|
||||||
|
doFirst {
|
||||||
|
copy {
|
||||||
|
from jar
|
||||||
|
into stageDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
baseName = 'jabit-server'
|
||||||
|
}
|
||||||
|
|
||||||
|
//frontend:build will be run before processResources
|
||||||
|
processResources {
|
||||||
|
dependsOn('frontend:build')
|
||||||
|
from('frontend/dist') {
|
||||||
|
//Public is a default supported Spring Boot resources directory.
|
||||||
|
into 'public'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
task wrapper(type: Wrapper) {
|
task wrapper(type: Wrapper) {
|
||||||
gradleVersion = '2.3'
|
gradleVersion = '2.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
57
frontend/.angular-cli.json
Normal file
57
frontend/.angular-cli.json
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"project": {
|
||||||
|
"name": "jabit-server"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
13
frontend/.editorconfig
Normal file
13
frontend/.editorconfig
Normal file
@ -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
|
42
frontend/.gitignore
vendored
Normal file
42
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - 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
|
28
frontend/README.md
Normal file
28
frontend/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# JabitServer
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.3.
|
||||||
|
|
||||||
|
## 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).
|
16
frontend/build.gradle
Normal file
16
frontend/build.gradle
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.moowork.node" version "1.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
node {
|
||||||
|
// Version of node to use.
|
||||||
|
version = '7.10.0'
|
||||||
|
|
||||||
|
// Enabled the automatic download.
|
||||||
|
download = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// runs "gulp build" as part of your gradle build
|
||||||
|
task build {
|
||||||
|
dependsOn npm_run_build
|
||||||
|
}
|
14
frontend/e2e/app.e2e-spec.ts
Normal file
14
frontend/e2e/app.e2e-spec.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { JabitServerPage } from './app.po';
|
||||||
|
|
||||||
|
describe('jabit-server App', () => {
|
||||||
|
let page: JabitServerPage;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
page = new JabitServerPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display message saying app works', () => {
|
||||||
|
page.navigateTo();
|
||||||
|
expect(page.getParagraphText()).toEqual('app works!');
|
||||||
|
});
|
||||||
|
});
|
11
frontend/e2e/app.po.ts
Normal file
11
frontend/e2e/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { browser, by, element } from 'protractor';
|
||||||
|
|
||||||
|
export class JabitServerPage {
|
||||||
|
navigateTo() {
|
||||||
|
return browser.get('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
getParagraphText() {
|
||||||
|
return element(by.css('app-root h1')).getText();
|
||||||
|
}
|
||||||
|
}
|
12
frontend/e2e/tsconfig.e2e.json
Normal file
12
frontend/e2e/tsconfig.e2e.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/e2e",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
44
frontend/karma.conf.js
Normal file
44
frontend/karma.conf.js
Normal file
@ -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
|
||||||
|
});
|
||||||
|
};
|
53
frontend/package.json
Normal file
53
frontend/package.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "jabit-server",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"e2e": "ng e2e"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^5.1.0",
|
||||||
|
"@angular/cdk": "^5.0.0",
|
||||||
|
"@angular/common": "^5.1.0",
|
||||||
|
"@angular/compiler": "^5.1.0",
|
||||||
|
"@angular/core": "^5.1.0",
|
||||||
|
"@angular/flex-layout": "^2.0.0-beta.10-4905443",
|
||||||
|
"@angular/forms": "^5.1.0",
|
||||||
|
"@angular/http": "^5.1.0",
|
||||||
|
"@angular/material": "^5.0.0",
|
||||||
|
"@angular/platform-browser": "^5.1.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^5.1.0",
|
||||||
|
"@angular/router": "^5.1.0",
|
||||||
|
"ajv": "^5.5.1",
|
||||||
|
"codelyzer": "^4.0.1",
|
||||||
|
"core-js": "^2.4.1",
|
||||||
|
"rxjs": "^5.5.4",
|
||||||
|
"tslint": "^5.8.0",
|
||||||
|
"zone.js": "^0.8.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/cli": "^1.5.5",
|
||||||
|
"@angular/compiler-cli": "^5.1.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.1.1",
|
||||||
|
"karma-cli": "~1.0.1",
|
||||||
|
"karma-coverage-istanbul-reporter": "^0.2.0",
|
||||||
|
"karma-jasmine": "~1.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
|
"protractor": "~5.1.0",
|
||||||
|
"ts-node": "~2.0.0",
|
||||||
|
"tslint": "~4.5.0",
|
||||||
|
"typescript": "~2.5.0"
|
||||||
|
}
|
||||||
|
}
|
30
frontend/protractor.conf.js
Normal file
30
frontend/protractor.conf.js
Normal file
@ -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 } }));
|
||||||
|
}
|
||||||
|
};
|
10
frontend/src/app/app.component.html
Normal file
10
frontend/src/app/app.component.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<mat-toolbar color="primary" class="mat-elevation-z6">
|
||||||
|
<div fxLayout fxLayoutAlign="space-between center" fxFlex>
|
||||||
|
{{title}}
|
||||||
|
<a mat-button title="Go to server status page" class="right"
|
||||||
|
routerLink="/status">
|
||||||
|
Status
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
<router-outlet></router-outlet>
|
32
frontend/src/app/app.component.spec.ts
Normal file
32
frontend/src/app/app.component.spec.ts
Normal file
@ -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!');
|
||||||
|
}));
|
||||||
|
});
|
10
frontend/src/app/app.component.ts
Normal file
10
frontend/src/app/app.component.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.css']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
title = 'Jabit Bitmessage Server';
|
||||||
|
}
|
38
frontend/src/app/app.module.ts
Normal file
38
frontend/src/app/app.module.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
import {MatButtonModule, MatCardModule, MatExpansionModule, MatToolbarModule} from '@angular/material';
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
import {StatusComponent} from './status/status.component';
|
||||||
|
import {APP_ROUTES} from "./app.routes";
|
||||||
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
|
import {BroadcastComponent} from './broadcast/broadcast.component';
|
||||||
|
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||||
|
import {FlexLayoutModule} from "@angular/flex-layout";
|
||||||
|
import {BackendService} from "./backend.service";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
StatusComponent,
|
||||||
|
BroadcastComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
HttpClientModule,
|
||||||
|
RouterModule.forRoot(APP_ROUTES),
|
||||||
|
FlexLayoutModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatExpansionModule,
|
||||||
|
MatButtonModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BackendService
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
15
frontend/src/app/app.routes.ts
Normal file
15
frontend/src/app/app.routes.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {Route} from "@angular/router";
|
||||||
|
import {StatusComponent} from "./status/status.component";
|
||||||
|
import {BroadcastComponent} from "./broadcast/broadcast.component";
|
||||||
|
|
||||||
|
export const APP_ROUTES: Route[] = [
|
||||||
|
{
|
||||||
|
path: 'status',
|
||||||
|
component: StatusComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'broadcasts/:address',
|
||||||
|
component: BroadcastComponent
|
||||||
|
},
|
||||||
|
{path: '', redirectTo: 'status', pathMatch: 'full'}
|
||||||
|
];
|
15
frontend/src/app/backend.service.spec.ts
Normal file
15
frontend/src/app/backend.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BackendService } from './backend.service';
|
||||||
|
|
||||||
|
describe('BackendService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [BackendService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([BackendService], (service: BackendService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
38
frontend/src/app/backend.service.ts
Normal file
38
frontend/src/app/backend.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Observable} from "rxjs/Observable";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {map} from "rxjs/operators";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BackendService {
|
||||||
|
|
||||||
|
private baseUrl = "http://localhost:8080";
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus(): Observable<string> {
|
||||||
|
return this.http.get(`${this.baseUrl}/api/v1/status`).pipe(map(data => JSON.stringify(data, null, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
getBroadcasts(address: string): Observable<Broadcasts> {
|
||||||
|
return this.http.get<Broadcasts>(`${this.baseUrl}/api/v1/read/${address}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Sender {
|
||||||
|
address: String;
|
||||||
|
alias: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
id: any;
|
||||||
|
received: number;
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Broadcasts {
|
||||||
|
sender: Sender;
|
||||||
|
messages: Message[]
|
||||||
|
}
|
16
frontend/src/app/broadcast/broadcast.component.html
Normal file
16
frontend/src/app/broadcast/broadcast.component.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<div *ngIf="broadcasts$ | async as broadcasts">
|
||||||
|
<h1>{{broadcasts.sender.alias || broadcasts.sender.address}}</h1>
|
||||||
|
<mat-expansion-panel *ngFor="let message of broadcasts.messages">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{message.subject}}
|
||||||
|
</mat-panel-title>
|
||||||
|
<mat-panel-description>
|
||||||
|
{{message.received * 1000 | date:'medium'}}
|
||||||
|
</mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<pre><code>{{message.body}}</code></pre>
|
||||||
|
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</div>
|
4
frontend/src/app/broadcast/broadcast.component.scss
Normal file
4
frontend/src/app/broadcast/broadcast.component.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:host {
|
||||||
|
margin: 2em;
|
||||||
|
display: block;
|
||||||
|
}
|
25
frontend/src/app/broadcast/broadcast.component.spec.ts
Normal file
25
frontend/src/app/broadcast/broadcast.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BroadcastComponent } from './broadcast.component';
|
||||||
|
|
||||||
|
describe('BroadcastComponent', () => {
|
||||||
|
let component: BroadcastComponent;
|
||||||
|
let fixture: ComponentFixture<BroadcastComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ BroadcastComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BroadcastComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
25
frontend/src/app/broadcast/broadcast.component.ts
Normal file
25
frontend/src/app/broadcast/broadcast.component.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {ActivatedRoute} from "@angular/router";
|
||||||
|
import {Observable} from "rxjs/Observable";
|
||||||
|
import {BackendService, Broadcasts} from "../backend.service";
|
||||||
|
import {map, switchMap} from "rxjs/operators";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-broadcast',
|
||||||
|
templateUrl: './broadcast.component.html',
|
||||||
|
styleUrls: ['./broadcast.component.scss']
|
||||||
|
})
|
||||||
|
export class BroadcastComponent implements OnInit {
|
||||||
|
|
||||||
|
broadcasts$: Observable<Broadcasts>;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private backend: BackendService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.broadcasts$ = this.route.params
|
||||||
|
.pipe(map(p => p['address']))
|
||||||
|
.pipe(switchMap(address => this.backend.getBroadcasts(address)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
frontend/src/app/status/status.component.html
Normal file
3
frontend/src/app/status/status.component.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<mat-card>
|
||||||
|
<pre><code>{{status$ | async}}</code></pre>
|
||||||
|
</mat-card>
|
4
frontend/src/app/status/status.component.scss
Normal file
4
frontend/src/app/status/status.component.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:host {
|
||||||
|
margin: 2em;
|
||||||
|
display: block;
|
||||||
|
}
|
25
frontend/src/app/status/status.component.spec.ts
Normal file
25
frontend/src/app/status/status.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StatusComponent } from './status.component';
|
||||||
|
|
||||||
|
describe('StatusComponent', () => {
|
||||||
|
let component: StatusComponent;
|
||||||
|
let fixture: ComponentFixture<StatusComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ StatusComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(StatusComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
18
frontend/src/app/status/status.component.ts
Normal file
18
frontend/src/app/status/status.component.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {BackendService} from "../backend.service";
|
||||||
|
import {Observable} from "rxjs/Observable";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-status',
|
||||||
|
templateUrl: './status.component.html',
|
||||||
|
styleUrls: ['./status.component.scss']
|
||||||
|
})
|
||||||
|
export class StatusComponent {
|
||||||
|
|
||||||
|
status$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(backend: BackendService) {
|
||||||
|
this.status$ = backend.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
frontend/src/assets/.gitkeep
Normal file
0
frontend/src/assets/.gitkeep
Normal file
3
frontend/src/environments/environment.prod.ts
Normal file
3
frontend/src/environments/environment.prod.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const environment = {
|
||||||
|
production: true
|
||||||
|
};
|
8
frontend/src/environments/environment.ts
Normal file
8
frontend/src/environments/environment.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// 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
|
||||||
|
};
|
BIN
frontend/src/favicon.ico
Normal file
BIN
frontend/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
16
frontend/src/index.html
Normal file
16
frontend/src/index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>JabitServer</title>
|
||||||
|
<base href="/">
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
|
||||||
|
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body class="mat-typography">
|
||||||
|
<app-root>Loading...</app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
frontend/src/main.ts
Normal file
11
frontend/src/main.ts
Normal file
@ -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);
|
72
frontend/src/polyfills.ts
Normal file
72
frontend/src/polyfills.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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`.
|
||||||
|
/**
|
||||||
|
* Need to import at least one locale-data with intl.
|
||||||
|
*/
|
||||||
|
// import 'intl/locale-data/jsonp/en';
|
6
frontend/src/styles.css
Normal file
6
frontend/src/styles.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
32
frontend/src/test.ts
Normal file
32
frontend/src/test.ts
Normal file
@ -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();
|
13
frontend/src/tsconfig.app.json
Normal file
13
frontend/src/tsconfig.app.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/app",
|
||||||
|
"module": "es2015",
|
||||||
|
"baseUrl": "",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
20
frontend/src/tsconfig.spec.json
Normal file
20
frontend/src/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/spec",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"baseUrl": "",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"test.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
5
frontend/src/typings.d.ts
vendored
Normal file
5
frontend/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* SystemJS module definition */
|
||||||
|
declare var module: NodeModule;
|
||||||
|
interface NodeModule {
|
||||||
|
id: string;
|
||||||
|
}
|
20
frontend/tsconfig.json
Normal file
20
frontend/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"baseUrl": "src",
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"target": "es5",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es2016",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
130
frontend/tslint.json
Normal file
130
frontend/tslint.json
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"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-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
|
||||||
|
}
|
||||||
|
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Mon Mar 16 20:59:34 CET 2015
|
#Thu Oct 19 11:25:29 CEST 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip
|
||||||
|
10
gradlew
vendored
10
gradlew
vendored
@ -42,11 +42,6 @@ case "`uname`" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
|
||||||
if $cygwin ; then
|
|
||||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
PRG="$0"
|
||||||
@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="`pwd`"
|
||||||
cd "`dirname \"$PRG\"`/" >&-
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
APP_HOME="`pwd -P`"
|
||||||
cd "$SAVED" >&-
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
@ -114,6 +109,7 @@ fi
|
|||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
3
settings.gradle
Normal file
3
settings.gradle
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
include 'frontend'
|
||||||
|
|
||||||
|
rootProject.name = 'jabit-server'
|
6
src/main/docker/Dockerfile
Normal file
6
src/main/docker/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM frolvlad/alpine-oraclejdk8:slim
|
||||||
|
VOLUME /tmp
|
||||||
|
ADD jabit-server.jar app.jar
|
||||||
|
RUN sh -c 'touch /app.jar'
|
||||||
|
ENV JAVA_OPTS=""
|
||||||
|
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
|
@ -1,61 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
|
|
||||||
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
|
|
||||||
import ch.dissem.bitmessage.repository.JdbcAddressRepository;
|
|
||||||
import ch.dissem.bitmessage.repository.JdbcConfig;
|
|
||||||
import ch.dissem.bitmessage.repository.JdbcInventory;
|
|
||||||
import ch.dissem.bitmessage.repository.JdbcMessageRepository;
|
|
||||||
import ch.dissem.bitmessage.security.bc.BouncySecurity;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
public class JabitServerApplication {
|
|
||||||
private BitmessageContext ctx;
|
|
||||||
|
|
||||||
@RequestMapping("status")
|
|
||||||
public String status() {
|
|
||||||
return ctx.status().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("read/{broadcastAddress}")
|
|
||||||
public List<Plaintext> read(@PathVariable String broadcastAddress) {
|
|
||||||
BitmessageAddress broadcaster = ctx.addresses().getAddress(broadcastAddress);
|
|
||||||
if (broadcaster == null) {
|
|
||||||
broadcaster = new BitmessageAddress(broadcastAddress);
|
|
||||||
}
|
|
||||||
if (!broadcaster.isSubscribed()) {
|
|
||||||
ctx.addSubscribtion(broadcaster);
|
|
||||||
}
|
|
||||||
return ctx.messages().findMessages(broadcaster);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JabitServerApplication() {
|
|
||||||
JdbcConfig config = new JdbcConfig();
|
|
||||||
ctx = new BitmessageContext.Builder()
|
|
||||||
.addressRepo(new JdbcAddressRepository(config))
|
|
||||||
.inventory(new JdbcInventory(config))
|
|
||||||
.messageRepo(new JdbcMessageRepository(config))
|
|
||||||
.nodeRegistry(new MemoryNodeRegistry())
|
|
||||||
.networkHandler(new DefaultNetworkHandler())
|
|
||||||
.security(new BouncySecurity())
|
|
||||||
.port(8445)
|
|
||||||
.build();
|
|
||||||
ctx.startup(plaintext -> {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(JabitServerApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server;
|
|
||||||
|
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
|
||||||
import org.springframework.boot.context.web.SpringBootServletInitializer;
|
|
||||||
|
|
||||||
public class ServletInitializer extends SpringBootServletInitializer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
|
||||||
return application.sources(JabitServerApplication.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server.entities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chrigu on 30.09.15.
|
|
||||||
*/
|
|
||||||
public class Broadcasts {
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server.entities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chrigu on 30.09.15.
|
|
||||||
*/
|
|
||||||
public class Message {
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server.entities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chrigu on 30.09.15.
|
|
||||||
*/
|
|
||||||
public class Sender {
|
|
||||||
}
|
|
13
src/main/kotlin/ch/dissem/bitmessage/server/Constants.kt
Normal file
13
src/main/kotlin/ch/dissem/bitmessage/server/Constants.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by chrigu on 22.11.15.
|
||||||
|
*/
|
||||||
|
object Constants {
|
||||||
|
val ADMIN_LIST = "admins.conf"
|
||||||
|
val CLIENT_LIST = "clients.conf"
|
||||||
|
|
||||||
|
val WHITELIST = "whitelist.conf"
|
||||||
|
val SHORTLIST = "shortlist.conf"
|
||||||
|
val BLACKLIST = "blacklist.conf"
|
||||||
|
}
|
55
src/main/kotlin/ch/dissem/bitmessage/server/Converter.kt
Normal file
55
src/main/kotlin/ch/dissem/bitmessage/server/Converter.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.server.entities.Broadcasts
|
||||||
|
import ch.dissem.bitmessage.server.entities.Message
|
||||||
|
import ch.dissem.bitmessage.server.entities.Sender
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
|
||||||
|
object Converter {
|
||||||
|
fun broadcasts(sender: BitmessageAddress, messages: Collection<Plaintext>) = Broadcasts().also {
|
||||||
|
it.sender = sender(sender)
|
||||||
|
it.messages = messages.map { message(it) }.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun broadcasts(sender: BitmessageAddress, vararg messages: Message) = Broadcasts().also {
|
||||||
|
it.sender = sender(sender)
|
||||||
|
it.messages = arrayOf(*messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sender(sender: BitmessageAddress) = Sender().apply {
|
||||||
|
address = sender.address
|
||||||
|
alias = sender.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun message(subject: String, body: String) = Message().also {
|
||||||
|
it.id = 0
|
||||||
|
it.received = UnixTime.now
|
||||||
|
it.subject = subject
|
||||||
|
it.body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
fun message(plaintext: Plaintext) = Message().apply {
|
||||||
|
id = plaintext.id
|
||||||
|
received = plaintext.received
|
||||||
|
subject = plaintext.subject
|
||||||
|
body = plaintext.text
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping
|
||||||
|
class FrontendController {
|
||||||
|
|
||||||
|
@RequestMapping("/{path:[^\\.]+}/**")
|
||||||
|
fun redirect(request: HttpServletRequest): String {
|
||||||
|
// Also forward to home page so that route is preserved.
|
||||||
|
return "forward:/index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||||
|
import springfox.documentation.swagger2.annotations.EnableSwagger2
|
||||||
|
|
||||||
|
@EnableWebMvc
|
||||||
|
@EnableSwagger2
|
||||||
|
@EnableScheduling
|
||||||
|
@SpringBootApplication
|
||||||
|
@ComponentScan(basePackageClasses = [JabitServerApplication::class])
|
||||||
|
class JabitServerApplication {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
SpringApplication.run(JabitServerApplication::class.java, *args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
193
src/main/kotlin/ch/dissem/bitmessage/server/JabitServerConfig.kt
Normal file
193
src/main/kotlin/ch/dissem/bitmessage/server/JabitServerConfig.kt
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler
|
||||||
|
import ch.dissem.bitmessage.repository.*
|
||||||
|
import ch.dissem.bitmessage.server.Constants.ADMIN_LIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.BLACKLIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.CLIENT_LIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.SHORTLIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.WHITELIST
|
||||||
|
import ch.dissem.bitmessage.server.repository.ServerProofOfWorkRepository
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.context.annotation.DependsOn
|
||||||
|
import springfox.documentation.spi.DocumentationType
|
||||||
|
import springfox.documentation.spring.web.plugins.Docket
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class JabitServerConfig {
|
||||||
|
|
||||||
|
@Value("\${bitmessage.port}")
|
||||||
|
private val port: Int = 0
|
||||||
|
@Value("\${bitmessage.connection.ttl.hours}")
|
||||||
|
private val connectionTTL: Long = 0
|
||||||
|
@Value("\${bitmessage.connection.limit}")
|
||||||
|
private val connectionLimit: Int = 0
|
||||||
|
|
||||||
|
@Value("\${database.url}")
|
||||||
|
private lateinit var dbUrl: String
|
||||||
|
@Value("\${database.user}")
|
||||||
|
private lateinit var dbUser: String
|
||||||
|
@Value("\${database.password}")
|
||||||
|
private var dbPassword: String? = null
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun jdbcConfig() = JdbcConfig(dbUrl, dbUser, dbPassword)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun addressRepo() = JdbcAddressRepository(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun inventory() = JdbcInventory(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun labelRepo() = JdbcLabelRepository(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun messageRepo() = JdbcMessageRepository(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun proofOfWorkRepo() = JdbcProofOfWorkRepository(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun nodeRegistry() = JdbcNodeRegistry(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun networkHandler() = NioNetworkHandler()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun cryptography() = BouncyCryptography().also {
|
||||||
|
Singleton.initialize(it) // needed for admins and clients
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun serverListener(): BitmessageContext.Listener =
|
||||||
|
ServerListener(
|
||||||
|
admins(), clients(),
|
||||||
|
whitelist(), shortlist(), blacklist()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun serverProofOfWorkRepository() = ServerProofOfWorkRepository(jdbcConfig())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun commandHandler() = ProofOfWorkRequestHandler(serverProofOfWorkRepository(), clients())
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun bitmessageContext() = BitmessageContext.build {
|
||||||
|
addressRepo = addressRepo()
|
||||||
|
inventory = inventory()
|
||||||
|
labelRepo = labelRepo()
|
||||||
|
messageRepo = messageRepo()
|
||||||
|
nodeRegistry = nodeRegistry()
|
||||||
|
proofOfWorkRepo = proofOfWorkRepo()
|
||||||
|
networkHandler = networkHandler()
|
||||||
|
listener = serverListener()
|
||||||
|
customCommandHandler = commandHandler()
|
||||||
|
cryptography = cryptography()
|
||||||
|
preferences.port = port
|
||||||
|
preferences.connectionLimit = connectionLimit
|
||||||
|
preferences.connectionTTL = connectionTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@DependsOn("cryptography")
|
||||||
|
fun admins() = Utils.readOrCreateList(
|
||||||
|
ADMIN_LIST,
|
||||||
|
"""# Admins can send commands to the server.
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
).map { BitmessageAddress(it) }.toMutableSet()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@DependsOn("cryptography")
|
||||||
|
fun clients() = Utils.readOrCreateList(
|
||||||
|
CLIENT_LIST,
|
||||||
|
"# Clients may send incomplete objects for proof of work.\n"
|
||||||
|
).map { BitmessageAddress(it) }.toMutableSet()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun whitelist() = Utils.readOrCreateList(
|
||||||
|
WHITELIST,
|
||||||
|
"""# If there are any Bitmessage addresses in the whitelist, only those will be shown.
|
||||||
|
|# blacklist.conf will be ignored, but shortlist.conf will be applied to whitelisted addresses.
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun shortlist() = Utils.readOrCreateList(
|
||||||
|
SHORTLIST,
|
||||||
|
""""# Broadcasts of these addresses will be restricted to the last $SHORTLIST_SIZE entries.
|
||||||
|
|
|
||||||
|
|# Time Service:
|
||||||
|
|BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash
|
||||||
|
|
|
||||||
|
|# Q's Aktivlist:
|
||||||
|
|BM-GtT7NLCCAu3HrT7dNTUTY9iDns92Z2ND
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun blacklist() = Utils.readOrCreateList(
|
||||||
|
BLACKLIST,
|
||||||
|
"""# Bitmessage addresses in this file are being ignored and their broadcasts won't be returned.
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun api(): Docket = Docket(DocumentationType.SWAGGER_2)
|
||||||
|
.select()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun identity(): BitmessageAddress {
|
||||||
|
val identities = bitmessageContext().addresses.getIdentities()
|
||||||
|
val identity: BitmessageAddress
|
||||||
|
if (identities.isEmpty()) {
|
||||||
|
LOG.info("Creating new identity...")
|
||||||
|
identity = bitmessageContext().createIdentity(false, Pubkey.Feature.DOES_ACK)
|
||||||
|
LOG.info("Identity ${identity.address} created.")
|
||||||
|
} else {
|
||||||
|
LOG.info("Identities:")
|
||||||
|
identities.forEach { LOG.info(it.address) }
|
||||||
|
identity = identities[0]
|
||||||
|
if (identities.size > 1) {
|
||||||
|
LOG.info("Using $identity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("QR Code:\n" + Utils.qrCode(identity))
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(JabitServerConfig::class.java)
|
||||||
|
|
||||||
|
val SHORTLIST_SIZE = 5
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.server.Converter.broadcasts
|
||||||
|
import ch.dissem.bitmessage.server.Converter.message
|
||||||
|
import ch.dissem.bitmessage.server.entities.Broadcasts
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod.GET
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import javax.annotation.PostConstruct
|
||||||
|
import javax.annotation.Resource
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
@CrossOrigin
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1")
|
||||||
|
class JabitServerController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var whitelist: Set<String>
|
||||||
|
@Resource
|
||||||
|
private lateinit var shortlist: Set<String>
|
||||||
|
@Resource
|
||||||
|
private lateinit var blacklist: Set<String>
|
||||||
|
@Inject
|
||||||
|
private lateinit var identity: BitmessageAddress
|
||||||
|
@Inject
|
||||||
|
private lateinit var ctx: BitmessageContext
|
||||||
|
|
||||||
|
@RequestMapping(value = ["identity"], method = [GET], produces = ["application/json"])
|
||||||
|
fun identity() = """{
|
||||||
|
"address": "${identity.address}",
|
||||||
|
"uri": "${Utils.getURL(identity, true)}"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
@RequestMapping(value = ["status"], method = [GET], produces = ["application/json"])
|
||||||
|
fun status() = "{${ctx.status()}}"
|
||||||
|
|
||||||
|
@RequestMapping(value = ["read/{broadcastAddress}"], method = [GET])
|
||||||
|
fun read(@PathVariable broadcastAddress: String): Broadcasts {
|
||||||
|
if ("test".equals(broadcastAddress, ignoreCase = true)) {
|
||||||
|
return broadcasts(
|
||||||
|
BitmessageAddress("BM-2cWhyaPxydemCeM8dWJUBmEo8iu7v2JptK"),
|
||||||
|
message("Test", "This is a test message. The rest service is running."),
|
||||||
|
message("Another Test", "And because it's such fun, a second message.")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var broadcaster = ctx.addresses.getAddress(broadcastAddress)
|
||||||
|
if (broadcaster == null) {
|
||||||
|
broadcaster = BitmessageAddress(broadcastAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!whitelist.isEmpty() && !whitelist.contains(broadcaster.address)) {
|
||||||
|
return broadcasts(broadcaster, message("Not Whitelisted", "Messages for " + broadcaster +
|
||||||
|
" can't be shown, as the sender isn't on the whitelist."))
|
||||||
|
}
|
||||||
|
if (blacklist.contains(broadcaster.address)) {
|
||||||
|
return broadcasts(broadcaster, message("Blacklisted", "Unfortunately, " + broadcaster +
|
||||||
|
" is on the blacklist, so it's messages can't be shown."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!broadcaster.isSubscribed) {
|
||||||
|
ctx.addSubscribtion(broadcaster)
|
||||||
|
}
|
||||||
|
val messages = ctx.messages.findMessages(broadcaster)
|
||||||
|
return if (shortlist.contains(broadcaster.address) && messages.size > SHORTLIST_SIZE) {
|
||||||
|
messages.listIterator(SHORTLIST_SIZE).forEach { ctx.messages.remove(it) }
|
||||||
|
broadcasts(broadcaster, messages.subList(0, SHORTLIST_SIZE))
|
||||||
|
} else {
|
||||||
|
broadcasts(broadcaster, messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 0 * * *")
|
||||||
|
fun broadcastStatus() = ctx.broadcast(identity, "Status", ctx.status().toString())
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 2 * * *")
|
||||||
|
fun cleanup() = ctx.cleanup()
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
fun setUp() = ctx.startup()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val SHORTLIST_SIZE = 5
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.CustomMessage
|
||||||
|
import ch.dissem.bitmessage.entity.MessagePayload
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATING
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE
|
||||||
|
import ch.dissem.bitmessage.ports.CustomCommandHandler
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
import ch.dissem.bitmessage.server.repository.ServerProofOfWorkRepository
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class ProofOfWorkRequestHandler(private val repo: ServerProofOfWorkRepository, clients: Collection<BitmessageAddress>) : CustomCommandHandler, InternalContext.ContextHolder {
|
||||||
|
|
||||||
|
private val decryptionKeys = clients.map { it.publicDecryptionKey }
|
||||||
|
private lateinit var engine: ProofOfWorkEngine
|
||||||
|
private lateinit var context: InternalContext
|
||||||
|
|
||||||
|
private val identity: BitmessageAddress by lazy {
|
||||||
|
context.addressRepository.getIdentities().firstOrNull() ?: BitmessageAddress(PrivateKey(
|
||||||
|
false,
|
||||||
|
context.streams[0],
|
||||||
|
InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||||
|
InternalContext.NETWORK_EXTRA_BYTES
|
||||||
|
)).also { context.addressRepository.save(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
Timer().schedule(15000) {
|
||||||
|
doMissingProofOfWork()
|
||||||
|
} // wait 15 seconds
|
||||||
|
Timer().schedule(60000, DAY * 1000) {
|
||||||
|
repo.cleanupTasks(7 * DAY)
|
||||||
|
} // First time after 1 minute, then daily
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doMissingProofOfWork() {
|
||||||
|
val incompleteTasks = repo.getIncompleteTasks()
|
||||||
|
LOG.info("Doing POW for ${incompleteTasks.size} tasks.")
|
||||||
|
for (task in incompleteTasks) {
|
||||||
|
engine.calculateNonce(task.initialHash, task.target) { initalHash, nonce -> repo.updateTask(initalHash, nonce) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(request: CustomMessage): MessagePayload? {
|
||||||
|
val cryptoMessage = CryptoCustomMessage.read(request) { client, input ->
|
||||||
|
ProofOfWorkRequest.read(client, input)
|
||||||
|
}
|
||||||
|
val decryptedRequest = decrypt(cryptoMessage) ?: return CustomMessage.error(
|
||||||
|
"Unknown sender. Please ask the server's administrator to add you as a client. For this he'll need your identity."
|
||||||
|
)
|
||||||
|
when (decryptedRequest.request) {
|
||||||
|
ProofOfWorkRequest.Request.CALCULATE -> {
|
||||||
|
if (!repo.hasTask(decryptedRequest.initialHash)) {
|
||||||
|
repo.storeTask(decryptedRequest)
|
||||||
|
// TODO: This is probably the place to do some book-keeping
|
||||||
|
// if we want to bill our customers.
|
||||||
|
engine.calculateNonce(decryptedRequest.initialHash, decryptedRequest.data) { initalHash, nonce -> repo.updateTask(initalHash, nonce) }
|
||||||
|
return CryptoCustomMessage(
|
||||||
|
ProofOfWorkRequest(identity, decryptedRequest.initialHash, CALCULATING, ByteArray(0))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val nonce = repo.getNonce(decryptedRequest)
|
||||||
|
return CryptoCustomMessage(
|
||||||
|
if (nonce != null) {
|
||||||
|
ProofOfWorkRequest(identity, decryptedRequest.initialHash, COMPLETE, nonce)
|
||||||
|
} else {
|
||||||
|
ProofOfWorkRequest(identity, decryptedRequest.initialHash, CALCULATING, ByteArray(0))
|
||||||
|
}
|
||||||
|
).apply { signAndEncrypt(identity, decryptedRequest.sender.pubkey!!.encryptionKey) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decrypt(cryptoMessage: CryptoCustomMessage<ProofOfWorkRequest>): ProofOfWorkRequest? {
|
||||||
|
for (key in decryptionKeys) {
|
||||||
|
try {
|
||||||
|
return cryptoMessage.decrypt(key)
|
||||||
|
} catch (_: DecryptionFailedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
this.context = context
|
||||||
|
this.engine = context.proofOfWorkEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(ProofOfWorkRequestHandler::class.java)
|
||||||
|
}
|
||||||
|
}
|
171
src/main/kotlin/ch/dissem/bitmessage/server/ServerListener.kt
Normal file
171
src/main/kotlin/ch/dissem/bitmessage/server/ServerListener.kt
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||||
|
import ch.dissem.bitmessage.server.Constants.ADMIN_LIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.BLACKLIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.CLIENT_LIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.SHORTLIST
|
||||||
|
import ch.dissem.bitmessage.server.Constants.WHITELIST
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class ServerListener(private val admins: MutableCollection<BitmessageAddress>,
|
||||||
|
private val clients: MutableCollection<BitmessageAddress>,
|
||||||
|
private val whitelist: MutableCollection<String>,
|
||||||
|
private val shortlist: MutableCollection<String>,
|
||||||
|
private val blacklist: MutableCollection<String>) : BitmessageContext.Listener.WithContext {
|
||||||
|
|
||||||
|
private lateinit var ctx: BitmessageContext
|
||||||
|
private val identity: BitmessageAddress by lazy {
|
||||||
|
val identities = ctx.addresses.getIdentities()
|
||||||
|
if (identities.isEmpty()) {
|
||||||
|
ctx.createIdentity(false)
|
||||||
|
} else {
|
||||||
|
identities[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setContext(ctx: BitmessageContext) {
|
||||||
|
this.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun receive(plaintext: Plaintext) {
|
||||||
|
if (admins.contains(plaintext.from)) {
|
||||||
|
val command = plaintext.subject!!.trim { it <= ' ' }.toLowerCase().split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
val data = plaintext.text
|
||||||
|
if (command.size == 1) {
|
||||||
|
when (command[0].toLowerCase()) {
|
||||||
|
"status" -> {
|
||||||
|
val response = Plaintext.Builder(MSG)
|
||||||
|
response.from(identity)
|
||||||
|
response.to(plaintext.from)
|
||||||
|
if (plaintext.encoding == EXTENDED) {
|
||||||
|
response.message(
|
||||||
|
Message.Builder()
|
||||||
|
.subject("RE: status")
|
||||||
|
.body(ctx.status().toString())
|
||||||
|
.addParent(plaintext)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
response.message("RE: status", ctx.status().toString())
|
||||||
|
}
|
||||||
|
ctx.send(response.build())
|
||||||
|
}
|
||||||
|
else -> LOG.info("ignoring unknown command " + plaintext.subject!!)
|
||||||
|
}
|
||||||
|
} else if (command.size == 2) {
|
||||||
|
when (command[1].toLowerCase()) {
|
||||||
|
"client", "clients" -> updateUserList(CLIENT_LIST, clients, command[0], data)
|
||||||
|
"admin", "admins", "administrator", "administrators" -> updateUserList(ADMIN_LIST, admins, command[0], data)
|
||||||
|
"whitelist" -> updateList(WHITELIST, whitelist, command[0], data)
|
||||||
|
"shortlist" -> updateList(SHORTLIST, shortlist, command[0], data)
|
||||||
|
"blacklist" -> updateList(BLACKLIST, blacklist, command[0], data)
|
||||||
|
else -> LOG.info("ignoring unknown command " + plaintext.subject!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUserList(file: String, list: MutableCollection<BitmessageAddress>, command: String, data: String?) {
|
||||||
|
when (command.toLowerCase()) {
|
||||||
|
"set" -> {
|
||||||
|
list.clear()
|
||||||
|
val scanner = Scanner(data!!)
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
val line = scanner.nextLine()
|
||||||
|
try {
|
||||||
|
list.add(BitmessageAddress(line))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.info("$command $file: ignoring line: $line")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Utils.saveList(file, list.map { it.address })
|
||||||
|
}
|
||||||
|
"add" -> {
|
||||||
|
val scanner = Scanner(data!!)
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
val line = scanner.nextLine()
|
||||||
|
try {
|
||||||
|
list.add(BitmessageAddress(line))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.info("$command $file: ignoring line: $line")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Utils.saveList(file, list.map { it.address })
|
||||||
|
}
|
||||||
|
"remove" -> {
|
||||||
|
list.removeIf { address -> data!!.contains(address.address) }
|
||||||
|
Utils.saveList(file, list.map { it.address })
|
||||||
|
}
|
||||||
|
else -> LOG.info("unknown command $command on list $file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateList(file: String, list: MutableCollection<String>, command: String, data: String?) {
|
||||||
|
when (command.toLowerCase()) {
|
||||||
|
"set" -> {
|
||||||
|
list.clear()
|
||||||
|
val scanner = Scanner(data!!)
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
val line = scanner.nextLine()
|
||||||
|
try {
|
||||||
|
list.add(BitmessageAddress(line).address)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.info("$command $file: ignoring line: $line")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Utils.saveList(file, list)
|
||||||
|
}
|
||||||
|
"add" -> {
|
||||||
|
val scanner = Scanner(data!!)
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
val line = scanner.nextLine()
|
||||||
|
try {
|
||||||
|
list.add(BitmessageAddress(line).address)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.info("$command $file: ignoring line: $line")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Utils.saveList(file, list)
|
||||||
|
}
|
||||||
|
"remove" -> {
|
||||||
|
list.removeAll { data!!.contains(it) }
|
||||||
|
Utils.saveList(file, list)
|
||||||
|
}
|
||||||
|
else -> LOG.info("unknown command $command on list $file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(ServerListener::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
|
||||||
|
|
||||||
|
class ServletInitializer : SpringBootServletInitializer() {
|
||||||
|
|
||||||
|
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
|
||||||
|
return application.sources(JabitServerApplication::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
129
src/main/kotlin/ch/dissem/bitmessage/server/Utils.kt
Normal file
129
src/main/kotlin/ch/dissem/bitmessage/server/Utils.kt
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import com.google.zxing.WriterException
|
||||||
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||||
|
import com.google.zxing.qrcode.encoder.Encoder
|
||||||
|
import com.google.zxing.qrcode.encoder.QRCode
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object Utils {
|
||||||
|
private val LOG = LoggerFactory.getLogger(Utils::class.java)
|
||||||
|
|
||||||
|
fun readOrCreateList(filename: String, content: String): MutableSet<String> {
|
||||||
|
val file = File(filename).apply {
|
||||||
|
if (!exists()) {
|
||||||
|
if (createNewFile()) {
|
||||||
|
FileWriter(this).use { fw -> fw.write(content) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readList(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveList(filename: String, content: Collection<String>) {
|
||||||
|
FileWriter(filename).use { fw ->
|
||||||
|
content.forEach { l ->
|
||||||
|
fw.write(l)
|
||||||
|
fw.write(System.lineSeparator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readList(file: File) = Files.readAllLines(file.toPath())
|
||||||
|
.map { it.trim { it <= ' ' } }
|
||||||
|
.filter { it.startsWith("BM-") }
|
||||||
|
.toMutableSet()
|
||||||
|
|
||||||
|
fun getURL(address: BitmessageAddress, includeKey: Boolean): String {
|
||||||
|
val attributes = mutableListOf<String>()
|
||||||
|
if (address.alias != null) {
|
||||||
|
attributes.add("label=${address.alias}")
|
||||||
|
}
|
||||||
|
if (includeKey) {
|
||||||
|
address.pubkey?.let { pubkey ->
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
pubkey.writer().writeUnencrypted(out)
|
||||||
|
attributes.add("pubkey=${Base64.getUrlEncoder().encodeToString(out.toByteArray())}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (attributes.isEmpty()) {
|
||||||
|
"bitmessage:${address.address}"
|
||||||
|
} else {
|
||||||
|
"bitmessage:${address.address}?${attributes.joinToString(separator = "&")}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun qrCode(address: BitmessageAddress): String {
|
||||||
|
val code: QRCode
|
||||||
|
try {
|
||||||
|
code = Encoder.encode(getURL(address, false), ErrorCorrectionLevel.L, null)
|
||||||
|
} catch (e: WriterException) {
|
||||||
|
LOG.error(e.message, e)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val matrix = code.matrix
|
||||||
|
val result = StringBuilder()
|
||||||
|
for (i in 0..1) {
|
||||||
|
for (j in 0 until matrix.width + 8) {
|
||||||
|
result.append('█')
|
||||||
|
}
|
||||||
|
result.append('\n')
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
var i = 0
|
||||||
|
while (i < matrix.height) {
|
||||||
|
result.append("████")
|
||||||
|
for (j in 0 until matrix.width) {
|
||||||
|
if (matrix.get(i, j) > 0) {
|
||||||
|
if (matrix.height > i + 1 && matrix.get(i + 1, j) > 0) {
|
||||||
|
result.append(' ')
|
||||||
|
} else {
|
||||||
|
result.append('▄')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (matrix.height > i + 1 && matrix.get(i + 1, j) > 0) {
|
||||||
|
result.append('▀')
|
||||||
|
} else {
|
||||||
|
result.append('█')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append("████\n")
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i in 0..1) {
|
||||||
|
for (j in 0 until matrix.width + 8) {
|
||||||
|
result.append('█')
|
||||||
|
}
|
||||||
|
result.append('\n')
|
||||||
|
}
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun zero(nonce: ByteArray) = nonce.none { it.toInt() != 0 }
|
||||||
|
}
|
20
src/main/kotlin/ch/dissem/bitmessage/server/WebMvcConfig.kt
Normal file
20
src/main/kotlin/ch/dissem/bitmessage/server/WebMvcConfig.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class WebMvcConfig : WebMvcConfigurer {
|
||||||
|
|
||||||
|
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||||
|
if (!registry.hasMappingForPattern("/webjars/**")) {
|
||||||
|
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/")
|
||||||
|
}
|
||||||
|
if (!registry.hasMappingForPattern("/**")) {
|
||||||
|
registry.addResourceHandler("/**").addResourceLocations("classpath:/public/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server.entities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON representation for the broadcasts of a specific sender
|
||||||
|
*/
|
||||||
|
data class Broadcasts(
|
||||||
|
var sender: Sender? = null,
|
||||||
|
var messages: Array<Message> = emptyArray()
|
||||||
|
)
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server.entities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON representation for plaintext messages
|
||||||
|
*/
|
||||||
|
data class Message(
|
||||||
|
var id: Any? = null,
|
||||||
|
var received: Long? = null,
|
||||||
|
var subject: String? = null,
|
||||||
|
var body: String? = null
|
||||||
|
)
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server.entities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON representation for a BitmessageAddress
|
||||||
|
*/
|
||||||
|
data class Sender(
|
||||||
|
var address: String? = null,
|
||||||
|
var alias: String? = null
|
||||||
|
)
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server.entities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
data class Update<T>(val oldValue: T, val newValue: T)
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.server.repository
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
||||||
|
import ch.dissem.bitmessage.repository.JdbcConfig
|
||||||
|
import ch.dissem.bitmessage.repository.JdbcHelper
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class ServerProofOfWorkRepository(config: JdbcConfig) : JdbcHelper(config) {
|
||||||
|
|
||||||
|
fun getIncompleteTasks(): List<Task> {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val result = LinkedList<Task>()
|
||||||
|
val rs = connection.createStatement().executeQuery(
|
||||||
|
"SELECT initial_hash, target FROM ProofOfWorkTask WHERE nonce IS NULL")
|
||||||
|
while (rs.next()) {
|
||||||
|
result.add(Task(
|
||||||
|
rs.getBytes(1),
|
||||||
|
rs.getBytes(2)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client (can be removed once the new IV is returned)
|
||||||
|
* IV (without nonce)
|
||||||
|
* IV (with nonce, can be removed once the new IV is returned)
|
||||||
|
* status: calculating, finished, confirmed
|
||||||
|
* data (can be removed once POW calculation is done)
|
||||||
|
*/
|
||||||
|
fun storeTask(request: ProofOfWorkRequest) {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val ps = connection.prepareStatement(
|
||||||
|
"INSERT INTO ProofOfWorkTask (initial_hash, client, target, timestamp) VALUES (?, ?, ?, ?)")
|
||||||
|
ps.setBytes(1, request.initialHash)
|
||||||
|
ps.setString(2, request.sender.address)
|
||||||
|
ps.setBytes(3, request.data)
|
||||||
|
ps.setLong(4, UnixTime.now)
|
||||||
|
ps.executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTask(initalHash: ByteArray, nonce: ByteArray) {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val ps = connection.prepareStatement(
|
||||||
|
"UPDATE ProofOfWorkTask SET nonce = ? WHERE initial_hash = ?")
|
||||||
|
ps.setBytes(1, nonce)
|
||||||
|
ps.setBytes(2, initalHash)
|
||||||
|
ps.executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNonce(request: ProofOfWorkRequest): ByteArray? {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val ps = connection.prepareStatement("SELECT nonce FROM ProofOfWorkTask WHERE initial_hash = ?")
|
||||||
|
ps.setBytes(1, request.initialHash)
|
||||||
|
val rs = ps.executeQuery()
|
||||||
|
return if (rs.next()) {
|
||||||
|
rs.getBytes(1)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasTask(initialHash: ByteArray): Boolean {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val ps = connection.prepareStatement("SELECT count(1) FROM ProofOfWorkTask WHERE initial_hash = ?")
|
||||||
|
ps.setBytes(1, initialHash)
|
||||||
|
val rs = ps.executeQuery()
|
||||||
|
rs.next()
|
||||||
|
return rs.getInt(1) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanupTasks(ageInSeconds: Long) {
|
||||||
|
config.getConnection().use { connection ->
|
||||||
|
val ps = connection.prepareStatement(
|
||||||
|
"DELETE FROM ProofOfWorkTask WHERE timestamp < ?")
|
||||||
|
ps.setLong(1, UnixTime.now - ageInSeconds)
|
||||||
|
ps.executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Task internal constructor(val initialHash: ByteArray, val target: ByteArray)
|
||||||
|
}
|
16
src/main/resources/application.yml
Normal file
16
src/main/resources/application.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#server.servlet.contextPath=/jabit-server
|
||||||
|
logging:
|
||||||
|
file: 'jabit.log'
|
||||||
|
level.*: ERROR
|
||||||
|
level.ch.dissem.*: WARN
|
||||||
|
|
||||||
|
bitmessage:
|
||||||
|
port: 8444
|
||||||
|
connection:
|
||||||
|
ttl.hours: 12
|
||||||
|
limit: 100
|
||||||
|
|
||||||
|
database:
|
||||||
|
url: 'jdbc:h2:file:./jabit;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=10'
|
||||||
|
user: 'sa'
|
||||||
|
password:
|
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE ProofOfWorkTask (
|
||||||
|
initial_hash BINARY(64) NOT NULL PRIMARY KEY,
|
||||||
|
client VARCHAR(40) NOT NULL,
|
||||||
|
target BINARY(32),
|
||||||
|
nonce BINARY(8),
|
||||||
|
timestamp BIGINT NOT NULL,
|
||||||
|
);
|
@ -1,18 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.server;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@SpringApplicationConfiguration(classes = JabitServerApplication.class)
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class JabitServerApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void contextLoads() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
package ch.dissem.bitmessage.server
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration
|
||||||
|
|
||||||
|
@WebAppConfiguration
|
||||||
|
@RunWith(SpringJUnit4ClassRunner::class)
|
||||||
|
@SpringBootTest(classes = [JabitServerApplication::class])
|
||||||
|
class JabitServerApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun contextLoads() = Unit
|
||||||
|
|
||||||
|
}
|
15
src/test/resources/application.yml
Normal file
15
src/test/resources/application.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
logging:
|
||||||
|
file: 'jabit.log'
|
||||||
|
level.*: DEBUG
|
||||||
|
level.ch.dissem.*: DEBUG
|
||||||
|
|
||||||
|
bitmessage:
|
||||||
|
port: 18444
|
||||||
|
connection:
|
||||||
|
ttl.hours: 12
|
||||||
|
limit: 100
|
||||||
|
|
||||||
|
database:
|
||||||
|
url: 'jdbc:h2:mem:jabit;DB_CLOSE_DELAY=10'
|
||||||
|
user: sa
|
||||||
|
password:
|
Loading…
Reference in New Issue
Block a user