--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/core/Feedback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.core;
19 |
20 | /**
21 | *
22 | * @author Alex Bogdanovski [alex@erudika.com]
23 | */
24 | public class Feedback extends Post {
25 | private static final long serialVersionUID = 1L;
26 |
27 | public Feedback() {
28 | super();
29 | }
30 |
31 | public enum FeedbackType {
32 | BUG,
33 | QUESTION,
34 | SUGGESTION;
35 |
36 | public String toString() {
37 | return super.toString().toLowerCase();
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 | para:
5 | image: erudikaltd/para:latest_stable
6 | ports:
7 | - "8080:8080"
8 | volumes:
9 | - type: volume
10 | source: paraData
11 | target: /para/data
12 | - type: volume
13 | source: paraLib
14 | target: /para/lib
15 | - type: bind
16 | source: ./para-application.conf
17 | target: /para/application.conf
18 | restart: always
19 | environment:
20 | - JAVA_OPTS=-Dconfig.file=/para/application.conf -Dloader.path=/para/lib
21 |
22 | scoold:
23 | depends_on:
24 | - para
25 | image: erudikaltd/scoold:latest_stable
26 | ports:
27 | - "8000:8000"
28 | volumes:
29 | - type: bind
30 | source: ./scoold-application.conf
31 | target: /scoold/application.conf
32 | - type: bind
33 | source: ./para-application.conf
34 | target: /scoold/para-application.conf
35 | restart: always
36 | environment:
37 | - JAVA_OPTS=-Dconfig.file=/scoold/application.conf -Dscoold.autoinit.para_config_file=/scoold/para-application.conf -Dscoold.para_endpoint=http://para:8080
38 | - BOOT_SLEEP=5
39 | volumes:
40 | paraData:
41 | paraLib:
42 |
--------------------------------------------------------------------------------
/src/main/resources/static/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 | http://www.scoold.com/
11 | daily
12 |
13 |
14 | http://www.scoold.com/about
15 | daily
16 |
17 |
18 | http://www.scoold.com/questions
19 | daily
20 |
21 |
22 | http://www.scoold.com/people
23 | daily
24 |
25 |
26 | http://www.scoold.com/feedback
27 | daily
28 |
29 |
30 | http://www.scoold.com/privacy
31 | daily
32 |
33 |
34 | http://www.scoold.com/terms
35 | daily
36 |
37 |
38 | http://www.scoold.com/languages
39 | daily
40 |
41 |
--------------------------------------------------------------------------------
/helm/scoold/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled }}
2 | apiVersion: networking.k8s.io/v1
3 | kind: Ingress
4 | metadata:
5 | name: {{ include "scoold.fullname" . }}
6 | labels:
7 | {{- include "scoold.labels" . | nindent 4 }}
8 | {{- with .Values.ingress.annotations }}
9 | annotations:
10 | {{- toYaml . | nindent 4 }}
11 | {{- end }}
12 | spec:
13 | {{- with .Values.ingress.className }}
14 | ingressClassName: {{ . }}
15 | {{- end }}
16 | {{- if .Values.ingress.tls }}
17 | tls:
18 | {{- range .Values.ingress.tls }}
19 | - hosts:
20 | {{- range .hosts }}
21 | - {{ . | quote }}
22 | {{- end }}
23 | secretName: {{ .secretName }}
24 | {{- end }}
25 | {{- end }}
26 | rules:
27 | {{- $fullName := include "scoold.fullname" . }}
28 | {{- range .Values.ingress.hosts }}
29 | - host: {{ .host | default $fullName | quote }}
30 | http:
31 | paths:
32 | {{- range .paths }}
33 | - path: {{ .path }}
34 | pathType: {{ .pathType | default "Prefix" }}
35 | backend:
36 | service:
37 | name: {{ $fullName }}
38 | port:
39 | number: {{ $.Values.service.port }}
40 | {{- end }}
41 | {{- end }}
42 | {{- end }}
43 |
--------------------------------------------------------------------------------
/src/main/resources/static/scripts/emoji-picker.js:
--------------------------------------------------------------------------------
1 | /* global picmoPopup */
2 |
3 | const emojiData = await (await fetch(CONTEXT_PATH + "/scripts/emojibase/data.json")).json();
4 | const messages = await (await fetch(CONTEXT_PATH + "/scripts/emojibase/messages.json")).json();
5 |
6 | $(document).on("click", ".emoji-button", function () {
7 | var cont = $(this).closest(".emoji-picker-container");
8 | if (cont.length) {
9 | if (!cont.data("emoji-picker")) {
10 | //var rootElem = $(this).closest("form").get(0) || cont.get(0);
11 | var picker = picmoPopup.createPopup({
12 | emojiData: emojiData,
13 | messages: messages,
14 | rootElement: cont.get(0),
15 | showCloseButton: false,
16 | showPreview: false,
17 | showSearch: true,
18 | hideOnClickOutside: true,
19 | showCategoryTabs: false,
20 | hideOnEmojiSelect: false,
21 | autoFocusSearch: true,
22 | emojisPerRow: 10,
23 | visibleRows: 7
24 | }, {
25 | className: "",
26 | position: "auto-end",
27 | triggerElement: this,
28 | referenceElement: this
29 | });
30 | picker.addEventListener("emoji:select", function (e) {
31 | cont.trigger("emoji:select", e);
32 | });
33 | cont.data("emoji-picker", picker);
34 | }
35 | cont.data("emoji-picker").toggle();
36 | }
37 | });
38 |
--------------------------------------------------------------------------------
/src/main/resources/themes/default.css:
--------------------------------------------------------------------------------
1 | /**** DEFAULT THEME ****/
2 | /* buttons, headings and links */
3 | a, .dropdown-content li>a, .dropdown-content li>span { color: #039be5; }
4 | .btn, .btn-large, .navbtn-hover { background-color: #ec407a; }
5 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #ec407a; opacity: 0.7; }
6 | .filter-active { background-color: #039be5; color: white; }
7 |
8 | /* nav bar and footer */
9 | body, footer.page-footer { background-color: #444444; }
10 | .navbar-color { background-color: #03a9f4; }
11 |
12 | /* switches and inputs */
13 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #039be5; }
14 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
15 | [type="radio"].with-gap:checked+span:after { border: 2px solid #039be5; }
16 | .switch label input[type=checkbox]:checked+.lever { background-color: #93dafa; }
17 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #03a9f4; }
18 |
19 | /* progress bars */
20 | .progress .indeterminate { background-color: #039be5; }
21 | .progress { background-color: #93dafa; }
22 |
23 | /* tabs */
24 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #039be5; }
25 | .tabs .indicator { background-color: #039be5; }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/velocity/VelocityConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.erudika.scoold.velocity;
18 |
19 | import org.apache.velocity.app.VelocityEngine;
20 |
21 | /**
22 | * Interface to be implemented by objects that configure and manage a
23 | * VelocityEngine for automatic lookup in a web environment. Detected
24 | * and used by VelocityView.
25 | *
26 | * @author Rod Johnson
27 | * @see VelocityConfigurer
28 | * @see VelocityView
29 | */
30 | public interface VelocityConfig {
31 |
32 | /**
33 | * Return the VelocityEngine for the current web application context.
34 | * May be unique to one servlet, or shared in the root context.
35 | * @return the VelocityEngine
36 | */
37 | VelocityEngine getVelocityEngine();
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/resources/themes/red.css:
--------------------------------------------------------------------------------
1 | /**** RED THEME ****/
2 | /* buttons, headings and links */
3 | a, .dropdown-content li>a, .dropdown-content li>span { color: #c62828; }
4 | .btn, .btn-large, .navbtn-hover { background-color: #37474f; }
5 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #37474f; opacity: 0.7; }
6 | .filter-active { background-color: #c62828; color: white; }
7 |
8 | /* nav bar and footer */
9 | body, footer.page-footer { background-color: #333333; }
10 | .navbar-color { background-color: #c62828; }
11 |
12 | /* switches and inputs */
13 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #c62828; }
14 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
15 | [type="radio"].with-gap:checked+span:after { border: 2px solid #c62828; }
16 | .switch label input[type=checkbox]:checked+.lever { background-color: #ff5f52; }
17 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #8e0000; }
18 | [type="checkbox"].filled-in:checked+span:not(.lever):after { border: 2px solid #c62828; background-color: #c62828; }
19 | [type="checkbox"]:checked+span:not(.lever):before { border-right: 2px solid #c62828; border-bottom: 2px solid #c62828; }
20 |
21 | /* progress bars */
22 | .progress .indeterminate { background-color: #c62828; }
23 | .progress { background-color: #ff5f52; }
24 |
25 | /* tabs */
26 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #c62828; }
27 | .tabs .indicator { background-color: #c62828; }
--------------------------------------------------------------------------------
/src/main/resources/themes/green.css:
--------------------------------------------------------------------------------
1 | /**** GREEN THEME ****/
2 | /* buttons, headings and links */
3 | a, .dropdown-content li>a, .dropdown-content li>span { color: #2e7d32; }
4 | .btn, .btn-large, .navbtn-hover { background-color: #4e342e; }
5 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #4e342e; opacity: 0.7; }
6 | .filter-active { background-color: #2e7d32; color: white; }
7 |
8 | /* nav bar and footer */
9 | body, footer.page-footer { background-color: #260e04; }
10 | .navbar-color { background-color: #2e7d32; }
11 |
12 | /* switches and inputs */
13 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #2e7d32; }
14 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
15 | [type="radio"].with-gap:checked+span:after { border: 2px solid #2e7d32; }
16 | .switch label input[type=checkbox]:checked+.lever { background-color: #60ad5e; }
17 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #005005; }
18 | [type="checkbox"].filled-in:checked+span:not(.lever):after { border: 2px solid #2e7d32; background-color: #2e7d32; }
19 | [type="checkbox"]:checked+span:not(.lever):before { border-right: 2px solid #2e7d32; border-bottom: 2px solid #2e7d32; }
20 |
21 | /* progress bars */
22 | .progress .indeterminate { background-color: #2e7d32; }
23 | .progress { background-color: #60ad5e; }
24 |
25 | /* tabs */
26 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #2e7d32; }
27 | .tabs .indicator { background-color: #2e7d32; }
--------------------------------------------------------------------------------
/src/main/resources/themes/orange.css:
--------------------------------------------------------------------------------
1 | /**** ORANGE THEME ****/
2 | /* buttons, headings and links */
3 | a, .dropdown-content li>a, .dropdown-content li>span { color: #ff9800;}
4 | a, button { font-weight: 400; }
5 | .btn, .btn-large, .navbtn-hover { background-color: #d84315; }
6 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #d84315; }
7 | .filter-active { background-color: #ff9800; color: white; }
8 |
9 | /* nav bar and footer */
10 | body, footer.page-footer { background-color: #333333; }
11 | .navbar-color { background-color: #ff9800; }
12 |
13 | /* switches and inputs */
14 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #ff9800; }
15 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
16 | [type="radio"].with-gap:checked+span:after { border: 2px solid #ff9800; }
17 | .switch label input[type=checkbox]:checked+.lever { background-color: #ff7543; }
18 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #9f0000; }
19 | [type="checkbox"].filled-in:checked+span:not(.lever):after { border: 2px solid #ff9800; background-color: #ff9800; }
20 | [type="checkbox"]:checked+span:not(.lever):before { border-right: 2px solid #ff9800; border-bottom: 2px solid #ff9800; }
21 |
22 | /* progress bars */
23 | .progress .indeterminate { background-color: #ff9800; }
24 | .progress { background-color: #ff7543; }
25 |
26 | /* tabs */
27 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #ff9800; }
28 | .tabs .indicator { background-color: #ff9800; }
29 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/src/main/resources/themes/blue.css:
--------------------------------------------------------------------------------
1 | /**** BLUE THEME ****/
2 | /* buttons, headings and links */
3 | a, .dropdown-content li>a, .dropdown-content li>span { color: #1565c0; }
4 | .btn, .btn-large, .navbtn-hover { background-color: #4fc3f7; }
5 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #4fc3f7; }
6 | .filter-active { background-color: #1565c0; color: white; }
7 |
8 | /* nav bar and footer */
9 | body, footer.page-footer { background-color: #efefef; }
10 | .navbar-color { background-color: #1565c0; }
11 | .page-footer .footer-copyright { color: #333333; }
12 | .page-footer .btn-flat { color: white; }
13 |
14 | /* switches and inputs */
15 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #1565c0; }
16 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
17 | [type="radio"].with-gap:checked+span:after { border: 2px solid #1565c0; }
18 | .switch label input[type=checkbox]:checked+.lever { background-color: #93dafa; }
19 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #1565c0; }
20 | [type="checkbox"].filled-in:checked+span:not(.lever):after { border: 2px solid #1565c0; background-color: #1565c0; }
21 | [type="checkbox"]:checked+span:not(.lever):before { border-right: 2px solid #1565c0; border-bottom: 2px solid #1565c0; }
22 |
23 | /* progress bars */
24 | .progress .indeterminate { background-color: #1565c0; }
25 | .progress { background-color: #93dafa; }
26 |
27 | /* tabs */
28 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #1565c0; }
29 | .tabs .indicator { background-color: #1565c0; }
30 |
--------------------------------------------------------------------------------
/src/main/resources/themes/mono.css:
--------------------------------------------------------------------------------
1 | /**** MONO THEME ****/
2 | /* buttons, headings and links */
3 | .dropdown-content li>a, .dropdown-content li>span { color: #424242; }
4 | a { color: #039be5; }
5 | .btn, .btn-large { background-color: #424242; }
6 | .navbtn-hover { background-color: #6d6d6d; }
7 | .btn:focus, .btn-large:focus, .btn-floating:focus { background-color: #424242; }
8 | .filter-active { background-color: #424242; color: white; }
9 |
10 | /* nav bar and footer */
11 | body, footer.page-footer { background-color: #444444; }
12 | .navbar-color { background-color: #424242; }
13 | .page-footer .footer-copyright { color: white; }
14 | .page-footer .btn-flat { color: white; }
15 |
16 | /* switches and inputs */
17 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after { background-color: #424242; }
18 | [type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before,
19 | [type="radio"].with-gap:checked+span:after { border: 2px solid #424242; }
20 | .switch label input[type=checkbox]:checked+.lever { background-color: #efefef; }
21 | .switch label input[type=checkbox]:checked+.lever:after { background-color: #8d8d8d; }
22 | [type="checkbox"].filled-in:checked+span:not(.lever):after { border: 2px solid #424242; background-color: #424242; }
23 | [type="checkbox"]:checked+span:not(.lever):before { border-right: 2px solid #424242; border-bottom: 2px solid #424242; }
24 |
25 | /* progress bars */
26 | .progress .indeterminate { background-color: #efefef; }
27 | .progress { background-color: #6d6d6d; }
28 |
29 | /* tabs */
30 | .tabs .tab a:hover, .tabs .tab a.active, .tabs .tab a { color: #424242; }
31 | .tabs .indicator { background-color: #424242; }
32 |
--------------------------------------------------------------------------------
/installer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e -x
3 |
4 | # Lightsail/DigitalOcean installer script for Ubuntu
5 | VERSION="1.65.0"
6 | PORT="8000"
7 | WORKDIR="/home/ubuntu"
8 | JARURL="https://github.com/Erudika/scoold/releases/download/${VERSION}/scoold-${VERSION}.jar"
9 | sfile="/etc/systemd/system/scoold.service"
10 |
11 | apt-get update && apt-get install -y wget openjdk-21-jre &&
12 | wget -O scoold.jar ${JARURL} && \
13 | mv scoold.jar $WORKDIR && \
14 | chown ubuntu:ubuntu ${WORKDIR}/scoold.jar && \
15 | chmod +x ${WORKDIR}/scoold.jar
16 | touch ${WORKDIR}/application.conf && \
17 | chown ubuntu:ubuntu ${WORKDIR}/application.conf
18 |
19 | # Feel free to modify the Scoold configuration here
20 | cat << EOF > ${WORKDIR}/application.conf
21 | scoold.app_name = "Scoold"
22 | scoold.port = 8000
23 | scoold.env = "production"
24 | scoold.host_url = "http://localhost:8000"
25 | scoold.para_endpoint = "https://paraio.com"
26 | scoold.para_access_key = "app:scoold"
27 | scoold.para_secret_key = ""
28 | scoold.admins = "admin@example.com"
29 | EOF
30 |
31 | touch $sfile
32 | cat << EOF > $sfile
33 | [Unit]
34 | Description=Scoold
35 | After=syslog.target
36 | StartLimitIntervalSec=30
37 | StartLimitBurst=2
38 | [Service]
39 | WorkingDirectory=${WORKDIR}
40 | SyslogIdentifier=Scoold
41 | ExecStart=java -jar -Dconfig.file=application.conf scoold.jar
42 | User=ubuntu
43 | Restart=on-failure
44 | RestartSec=1s
45 | [Install]
46 | WantedBy=multi-user.target
47 | EOF
48 |
49 | # This is optional. These rules might interfere with other web server configurations like nginx and certbot.
50 | #iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-port ${PORT} && \
51 | #iptables -t nat -A OUTPUT -p tcp --dport 80 -o lo -j REDIRECT --to-port ${PORT}
52 |
53 | systemctl enable scoold.service && \
54 | systemctl start scoold.service
55 |
--------------------------------------------------------------------------------
/helm/scoold/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "scoold.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | If release name contains chart name it will be used as a full name.
13 | */}}
14 | {{- define "scoold.fullname" -}}
15 | {{- if .Values.fullnameOverride -}}
16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- $name := default .Chart.Name .Values.nameOverride -}}
19 | {{- if contains $name .Release.Name -}}
20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
21 | {{- else -}}
22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
23 | {{- end -}}
24 | {{- end -}}
25 | {{- end -}}
26 |
27 | {{/*
28 | Create chart name and version as used by the chart label.
29 | */}}
30 | {{- define "scoold.chart" -}}
31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
32 | {{- end -}}
33 |
34 | {{/*
35 | Common labels
36 | */}}
37 | {{- define "scoold.labels" -}}
38 | helm.sh/chart: {{ include "scoold.chart" . }}
39 | app.kubernetes.io/name: {{ include "scoold.name" . }}
40 | app.kubernetes.io/instance: {{ .Release.Name }}
41 | app.kubernetes.io/managed-by: {{ .Release.Service }}
42 | {{- with .Chart.AppVersion }}
43 | app.kubernetes.io/version: {{ . | quote }}
44 | {{- end }}
45 | {{- end -}}
46 |
47 | {{/*
48 | Selector labels
49 | */}}
50 | {{- define "scoold.selectorLabels" -}}
51 | app.kubernetes.io/name: {{ include "scoold.name" . }}
52 | app.kubernetes.io/instance: {{ .Release.Name }}
53 | {{- end -}}
54 |
55 |
--------------------------------------------------------------------------------
/src/main/java-templates/com/erudika/scoold/utils/Version.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils;
19 |
20 | /**
21 | * GENERATED CLASS. DO NOT MODIFY!
22 | * @author Alex Bogdanovski [alex@erudika.com]
23 | */
24 | public final class Version {
25 |
26 | private static final String VERSION = "${project.version}";
27 | private static final String GROUPID = "${project.groupId}";
28 | private static final String ARTIFACTID = "${project.artifactId}";
29 | private static final String GIT = "${project.scm.developerConnection}";
30 | private static final String GIT_BRANCH = "${scmBranch}";
31 | private static final String REVISION = "${buildNumber}";
32 |
33 | public static String getVersion() {
34 | return VERSION;
35 | }
36 |
37 | public static String getArtifactId() {
38 | return ARTIFACTID;
39 | }
40 |
41 | public static String getGroupId() {
42 | return GROUPID;
43 | }
44 |
45 | public static String getGIT() {
46 | return GIT;
47 | }
48 |
49 | public static String getRevision() {
50 | return REVISION;
51 | }
52 |
53 | public static String getGITBranch() {
54 | return GIT_BRANCH;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/controllers/NotFoundController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.controllers;
19 |
20 | import com.erudika.scoold.utils.ScooldUtils;
21 | import jakarta.inject.Inject;
22 | import jakarta.servlet.http.HttpServletRequest;
23 | import jakarta.servlet.http.HttpServletResponse;
24 | import org.springframework.stereotype.Controller;
25 | import org.springframework.ui.Model;
26 | import org.springframework.web.bind.annotation.GetMapping;
27 | import org.springframework.web.bind.annotation.RequestMapping;
28 |
29 | /**
30 | *
31 | * @author Alex Bogdanovski [alex@erudika.com]
32 | */
33 | @Controller
34 | @RequestMapping("/not-found")
35 | public class NotFoundController {
36 |
37 | private final ScooldUtils utils;
38 |
39 | @Inject
40 | public NotFoundController(ScooldUtils utils) {
41 | this.utils = utils;
42 | }
43 |
44 | @GetMapping
45 | public String get(HttpServletRequest req, HttpServletResponse res, Model model) {
46 | model.addAttribute("path", "notfound.vm");
47 | model.addAttribute("title", utils.getLang(req).get("notfound.title"));
48 | res.setStatus(404);
49 | return "base";
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/velocity/VelocityViewResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.erudika.scoold.velocity;
17 |
18 | import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
19 | import org.springframework.web.servlet.view.AbstractUrlBasedView;
20 |
21 | /**
22 | *
23 | * The view class for all views generated by this resolver can be specified via the "viewClass" property. See
24 | * UrlBasedViewResolver's javadoc for details.
25 | *
26 | *
27 | * Note: When chaining ViewResolvers, a VelocityViewResolver will check for the existence of the specified
28 | * template resources and only return a non-null View object if the template was actually found.
29 | *
30 | * @author Juergen Hoeller
31 | * @since 13.12.2003
32 | */
33 | public class VelocityViewResolver extends AbstractTemplateViewResolver {
34 |
35 | public VelocityViewResolver() {
36 | setViewClass(requiredViewClass());
37 | }
38 |
39 | @Override
40 | protected Class> requiredViewClass() {
41 | return VelocityView.class;
42 | }
43 |
44 |
45 | @Override
46 | protected AbstractUrlBasedView buildView(String viewName) throws Exception {
47 | VelocityView view = (VelocityView) super.buildView(viewName);
48 | return view;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/helm/scoold/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | __ __
2 | ______________ ____ / /___/ /
3 | / ___/ ___/ __ \/ __ \/ / __ /
4 | (__ ) /__/ /_/ / /_/ / / /_/ /
5 | /____/\___/\____/\____/_/\__,_/
6 |
7 | 1. Get the application URL by running these commands:
8 | {{- if .Values.ingress.enabled }}
9 | {{- range $host := .Values.ingress.hosts }}
10 | {{- $hostName := $host.host | default (include "scoold.fullname" $) }}
11 | {{- range $path := $host.paths }}
12 | Visit http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $hostName }}{{ $path.path }} to use your application
13 | {{- end }}
14 | {{- end }}
15 | {{- else if contains "NodePort" .Values.service.type }}
16 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "scoold.fullname" . }})
17 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
18 | echo http://$NODE_IP:$NODE_PORT
19 | {{- else if contains "LoadBalancer" .Values.service.type }}
20 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
21 | You can watch the status of by running 'kubectl get svc -w {{ include "scoold.fullname" . }}'
22 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "scoold.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
23 | echo http://$SERVICE_IP:{{ .Values.service.port }}
24 | {{- else if contains "ClusterIP" .Values.service.type }}
25 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "scoold.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
26 | kubectl port-forward $POD_NAME {{ .Values.service.port }}:{{ .Values.service.port }}
27 | {{- end }}
28 | 2. Visit http://localhost:{{ .Values.service.port }} to use your application
29 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/DefaultAvatarRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import com.erudika.scoold.utils.ScooldUtils;
22 | import org.apache.commons.lang3.StringUtils;
23 | import org.apache.commons.lang3.Strings;
24 |
25 | public class DefaultAvatarRepository implements AvatarRepository {
26 |
27 | public DefaultAvatarRepository() {
28 | }
29 |
30 | @Override
31 | public String getLink(Profile profile, AvatarFormat format) {
32 | return (profile == null || StringUtils.isBlank(profile.getPicture())) ? ScooldUtils.getDefaultAvatar() : profile.getPicture();
33 | }
34 |
35 | @Override
36 | public String getAnonymizedLink(String data) {
37 | return ScooldUtils.getDefaultAvatar();
38 | }
39 |
40 | @Override
41 | public boolean store(Profile profile, String url) {
42 | if (StringUtils.isBlank(url) || !url.equalsIgnoreCase(profile.getOriginalPicture())) {
43 | if (Strings.CI.startsWith(url, ScooldUtils.getConfig().serverUrl())) {
44 | profile.setPicture(url);
45 | } else {
46 | profile.setPicture(ScooldUtils.getDefaultAvatar());
47 | }
48 | } else {
49 | profile.setPicture(profile.getOriginalPicture());
50 | }
51 | return true;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/latest.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Docker Hub - latest
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | # Allows you to run this workflow manually from the Actions tab
11 | workflow_dispatch:
12 |
13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
14 | jobs:
15 | # This workflow contains a single job called "build"
16 | build:
17 | # The type of runner that the job will run on
18 | runs-on: ubuntu-latest
19 |
20 | # Steps represent a sequence of tasks that will be executed as part of the job
21 | steps:
22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
23 | - name: Check Out Repo
24 | uses: actions/checkout@v4
25 |
26 | - name: Login to Docker Hub
27 | uses: docker/login-action@v3
28 | with:
29 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
30 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
31 |
32 | - name: Set up Docker Buildx
33 | id: buildx
34 | uses: docker/setup-buildx-action@v3
35 |
36 | - name: Build and push
37 | id: docker_build
38 | uses: docker/build-push-action@v6
39 | with:
40 | context: ./
41 | file: ./Dockerfile
42 | builder: ${{ steps.buildx.outputs.name }}
43 | push: true
44 | tags: erudikaltd/scoold:latest
45 | cache-from: type=local,src=/tmp/.buildx-cache
46 | cache-to: type=local,dest=/tmp/.buildx-cache
47 |
48 | - name: Image digest
49 | run: echo ${{ steps.docker_build.outputs.digest }}
50 |
51 | - name: Cache Docker layers
52 | uses: actions/cache@v4
53 | with:
54 | path: /tmp/.buildx-cache
55 | key: ${{ runner.os }}-buildx-${{ github.sha }}
56 | restore-keys: |
57 | ${{ runner.os }}-buildx-
58 |
--------------------------------------------------------------------------------
/src/test/java/com/erudika/scoold/utils/avatars/DefaultAvatarRepositoryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import com.erudika.scoold.utils.ScooldUtils;
22 | import static org.junit.Assert.*;
23 | import org.junit.Before;
24 | import org.junit.Test;
25 |
26 | public class DefaultAvatarRepositoryTest {
27 | private DefaultAvatarRepository repository;
28 |
29 | @Before
30 | public void setUp(){
31 | this.repository = new DefaultAvatarRepository();
32 | }
33 |
34 | @Test
35 | public void getLink_should_return_always_default_avatar() {
36 | Profile profile = new Profile();
37 | assertEquals(ScooldUtils.getDefaultAvatar(), repository.getLink(profile, AvatarFormat.Profile));
38 | assertEquals(repository.getLink(new Profile(), AvatarFormat.Square32), repository.getLink(new Profile(), AvatarFormat.Profile));
39 | }
40 |
41 | @Test
42 | public void getAnonymizedLink_should_always_return_default_avatar() {
43 | assertEquals(ScooldUtils.getDefaultAvatar(), repository.getAnonymizedLink("A"));
44 | assertEquals(repository.getAnonymizedLink("A"), repository.getAnonymizedLink("B"));
45 | }
46 |
47 | @Test
48 | public void store_should_nothing() {
49 | Profile profile = new Profile();
50 |
51 | boolean result = repository.store(profile, "https://avatar");
52 | assertEquals(ScooldUtils.getDefaultAvatar(), profile.getPicture());
53 | assertEquals(true, result);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/ImgurAvatarRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import com.erudika.scoold.utils.ScooldUtils;
22 | import org.apache.commons.lang3.StringUtils;
23 | import org.apache.commons.lang3.Strings;
24 |
25 |
26 | public class ImgurAvatarRepository implements AvatarRepository {
27 |
28 | private final AvatarRepository nextRepository;
29 |
30 | public ImgurAvatarRepository(AvatarRepository nextRepository) {
31 | this.nextRepository = nextRepository;
32 | }
33 |
34 | @Override
35 | public String getLink(Profile profile, AvatarFormat format) {
36 | if (profile == null || StringUtils.isBlank(profile.getPicture())) {
37 | return nextRepository.getLink(profile, format);
38 | }
39 | String picture = profile.getPicture();
40 | if (isImgurLink(picture)) {
41 | return picture;
42 | }
43 | return ScooldUtils.getDefaultAvatar();
44 | }
45 |
46 | private boolean isImgurLink(String picture) {
47 | return Strings.CI.startsWith(picture, "https://i.imgur.com/");
48 | }
49 |
50 | @Override
51 | public String getAnonymizedLink(String data) {
52 | return nextRepository.getAnonymizedLink(data);
53 | }
54 |
55 | @Override
56 | public boolean store(Profile profile, String url) {
57 | if (!isImgurLink(url)) {
58 | return nextRepository.store(profile, url);
59 | }
60 | profile.setPicture(url);
61 | return true;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/.github/workflows/tag.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Docker Hub - tagged
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | tags:
10 | - "*.*.*"
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
15 | jobs:
16 | # This workflow contains a single job called "build"
17 | build:
18 | # The type of runner that the job will run on
19 | runs-on: ubuntu-latest
20 |
21 | # Steps represent a sequence of tasks that will be executed as part of the job
22 | steps:
23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24 | - name: Check Out Repo
25 | uses: actions/checkout@v4
26 | - name: Set env
27 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
28 |
29 | - name: Login to Docker Hub
30 | uses: docker/login-action@v3
31 | with:
32 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
33 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
34 |
35 | - name: Set up Docker Buildx
36 | id: buildx
37 | uses: docker/setup-buildx-action@v3
38 |
39 | - name: Build and push
40 | id: docker_build
41 | uses: docker/build-push-action@v6
42 | with:
43 | context: ./
44 | file: ./Dockerfile
45 | builder: ${{ steps.buildx.outputs.name }}
46 | push: true
47 | tags: erudikaltd/scoold:${{ env.RELEASE_VERSION }}, erudikaltd/scoold:latest_stable
48 | cache-from: type=local,src=/tmp/.buildx-cache
49 | cache-to: type=local,dest=/tmp/.buildx-cache
50 |
51 | - name: Image digest
52 | run: echo ${{ steps.docker_build.outputs.digest }}
53 |
54 | - name: Cache Docker layers
55 | uses: actions/cache@v4
56 | with:
57 | path: /tmp/.buildx-cache
58 | key: ${{ runner.os }}-buildx-${{ github.sha }}
59 | restore-keys: |
60 | ${{ runner.os }}-buildx-
61 |
--------------------------------------------------------------------------------
/gencerts.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | NAME=$1 # Use your own domain name
4 | SECRET=$2 # Keystore password
5 | CAFILE=$3 # File name of the CA cert and key
6 |
7 | read -e -p "Certificate alias: " alias
8 | alias=$(echo "$alias" | awk '{print tolower($0)}')
9 | ######################
10 | # Become a CA or use existing one
11 | ######################
12 |
13 | if [[ -z "$SECRET" ]]; then
14 | SECRET="secret"
15 | fi
16 |
17 | if [[ -z "$CAFILE" ]]; then
18 | CAFILE="${alias^}RootCA"
19 | # Generate root certificate
20 | openssl req -x509 -new -nodes -sha256 -days 1024 -newkey rsa:2048 -keyout $CAFILE.key -out $CAFILE.pem -subj "/C=BG/CN=$CAFILE"
21 | # Create a Windows-compatible crt file
22 | openssl x509 -outform pem -in $CAFILE.pem -out $CAFILE.crt
23 | fi
24 |
25 | ######################
26 | # Create CA-signed certs
27 | ######################
28 |
29 | # Create a certificate-signing request
30 | openssl req -new -nodes -newkey rsa:2048 -keyout $NAME.key -out $NAME.csr -subj "/C=BG/ST=EU/L=Sofia/O=Erudika/CN=$NAME"
31 | # Create a config file for the extensions
32 | >$NAME.ext cat <<-EOF
33 | authorityKeyIdentifier=keyid,issuer
34 | basicConstraints=CA:FALSE
35 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
36 | subjectAltName = @alt_names
37 | [alt_names]
38 | DNS.1 = $NAME # Be sure to include the domain name here because Common Name is not so commonly honoured by itself
39 | #IP.1 = 192.168.0.10 # Optionally, add an IP address (if the connection which you have planned requires it)
40 | EOF
41 | # Create the signed certificate
42 | openssl x509 -req -sha256 -days 1024 -in $NAME.csr -CA $CAFILE.pem -CAkey $CAFILE.key -CAcreateserial -extfile $NAME.ext -out $NAME.pem
43 | # Create a Windows-compatible crt file
44 | openssl x509 -outform pem -in $NAME.pem -out $NAME.crt
45 | # Clean up
46 | rm $NAME.csr $NAME.ext
47 |
48 | ######################
49 | # Create Java Keystore
50 | ######################
51 | openssl pkcs12 -export -out ${alias}-keystore.p12 -in $NAME.pem -inkey $NAME.key -name ${alias} -passin pass:$SECRET -passout pass:$SECRET
52 |
53 | ######################
54 | # Create Java Truststore
55 | ######################
56 | keytool -v -importcert -file $CAFILE.pem -alias root-ca -keystore ${alias}-truststore.p12 -storepass $SECRET -noprompt
57 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/GravatarAvatarGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.para.core.utils.Utils;
21 | import com.erudika.scoold.core.Profile;
22 | import com.erudika.scoold.utils.ScooldUtils;
23 | import jakarta.inject.Singleton;
24 | import org.apache.commons.lang3.StringUtils;
25 | import org.apache.commons.lang3.Strings;
26 | import org.springframework.stereotype.Component;
27 |
28 | @Component
29 | @Singleton
30 | public class GravatarAvatarGenerator {
31 | private static final String URL_BASE = "https://www.gravatar.com/avatar/";
32 |
33 | public GravatarAvatarGenerator() {
34 | }
35 |
36 | public String getLink(Profile profile, AvatarFormat format) {
37 | return configureLink(getRawLink(profile), format);
38 | }
39 |
40 | public String getRawLink(Profile profile) {
41 | String email = (profile == null || profile.getUser() == null) ? "" : profile.getUser().getEmail();
42 | return getRawLink(email);
43 | }
44 |
45 | public String getRawLink(String email) {
46 | return URL_BASE + computeToken(email);
47 | }
48 |
49 | private String computeToken(String email) {
50 | if (StringUtils.isBlank(email)) {
51 | return "";
52 | }
53 |
54 | return Utils.md5(email.toLowerCase());
55 | }
56 |
57 | public String configureLink(String url, AvatarFormat format) {
58 | return url + (url.endsWith("?") ? "&" : "?") + "s=" + format.getSize() + "&r=g&d=" + ScooldUtils.gravatarPattern();
59 | }
60 |
61 | public boolean isLink(String link) {
62 | return Strings.CS.contains(link, "gravatar.com");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "name": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "Name for the container group"
9 | },
10 | "defaultValue": "scoold"
11 | },
12 | "image": {
13 | "type": "string",
14 | "metadata": {
15 | "description": "Container image to deploy."
16 | },
17 | "defaultValue": "erudikaltd/scoold:latest_stable"
18 | },
19 | "port": {
20 | "type": "string",
21 | "metadata": {
22 | "description": "Port to open on the container and the public IP address."
23 | },
24 | "defaultValue": "8000"
25 | },
26 | "cpuCores": {
27 | "type": "string",
28 | "metadata": {
29 | "description": "The number of CPU cores to allocate to the container. Must be an integer."
30 | },
31 | "defaultValue": "1.0"
32 | },
33 | "memoryInGb": {
34 | "type": "string",
35 | "metadata": {
36 | "description": "The amount of memory to allocate to the container in gigabytes."
37 | },
38 | "defaultValue": "1.0"
39 | }
40 | },
41 | "variables": {},
42 | "resources": [
43 | {
44 | "name": "[parameters('name')]",
45 | "type": "Microsoft.ContainerInstance/containerGroups",
46 | "apiVersion": "2018-10-01",
47 | "location": "[resourceGroup().location]",
48 | "properties": {
49 | "containers": [
50 | {
51 | "name": "[parameters('name')]",
52 | "properties": {
53 | "image": "[parameters('image')]",
54 | "ports": [
55 | {
56 | "port": "[parameters('port')]"
57 | }
58 | ],
59 | "resources": {
60 | "requests": {
61 | "cpu": "[parameters('cpuCores')]",
62 | "memoryInGb": "[parameters('memoryInGb')]"
63 | }
64 | }
65 | }
66 | }
67 | ],
68 | "osType": "Linux",
69 | "ipAddress": {
70 | "type": "Public",
71 | "ports": [
72 | {
73 | "protocol": "tcp",
74 | "port": "[parameters('port')]"
75 | }
76 | ]
77 | }
78 | }
79 | }
80 | ],
81 | "outputs": {
82 | "containerIPv4Address": {
83 | "type": "string",
84 | "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('name'))).ipAddress.ip]"
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/helm/scoold/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for Scoold.
2 | # Declare variables to be passed into your templates.
3 |
4 | replicaCount: 1
5 |
6 | nameOverride: ""
7 | fullnameOverride: ""
8 |
9 | #########################################
10 | # Docker image - use "latest" for current
11 | # snapshot of the master branch
12 | #########################################
13 | image:
14 | repository: erudikaltd/scoold
15 | tag: "1.65.0"
16 | pullPolicy: IfNotPresent
17 | pullSecrets: []
18 |
19 | service:
20 | name: http
21 | type: ClusterIP
22 | port: 8000
23 |
24 | ######################
25 | # Scoold configuration
26 | ######################
27 | applicationConf: |
28 | scoold.env = production
29 | scoold.para_endpoint = "https://paraio.com"
30 | scoold.para_access_key = "app:scoold"
31 | scoold.para_secret_key = "secret"
32 | #scoold.password_auth_enabled = true
33 | #scoold.is_default_space_public = true
34 | ####################################
35 | # Add more config properties here...
36 | ####################################
37 |
38 | ###################################################
39 | # JVM command line arguments, exported as JAVA_OPTS
40 | ###################################################
41 | javaOpts: "-Xmx512m -Xms512m -Dconfig.file=/scoold/config/application.conf"
42 |
43 | podAnnotations: {}
44 |
45 | #####################
46 | # Extra ENV variables
47 | #####################
48 | extraEnvs: []
49 |
50 | updateStrategy: RollingUpdate
51 |
52 | ingress:
53 | enabled: false
54 | className: ""
55 | annotations:
56 | kubernetes.io/ingress.class: nginx
57 | # kubernetes.io/tls-acme: "true"
58 | hosts:
59 | - host: scoold.local
60 | paths:
61 | - path: /
62 | pathType: Prefix
63 | tls: []
64 | # - secretName: scoold-tls-secret
65 | # hosts:
66 | # - scoold.local
67 |
68 | resources: {}
69 | # We usually recommend not to specify default resources and to leave this as a conscious
70 | # choice for the user. This also increases chances charts run on environments with little
71 | # resources, such as Minikube. If you do want to specify resources, uncomment the following
72 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
73 | # limits:
74 | # cpu: 100m
75 | # memory: 128Mi
76 | # requests:
77 | # cpu: 100m
78 | # memory: 128Mi
79 |
80 | nodeSelector: {}
81 |
82 | tolerations: []
83 |
84 | affinity: {}
85 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/CloudinaryAvatarRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import org.apache.commons.lang3.Strings;
22 |
23 | public class CloudinaryAvatarRepository implements AvatarRepository {
24 | private static final String BASE_URL = "https://res.cloudinary.com/";
25 |
26 | private final AvatarRepository nextRepository;
27 |
28 | public CloudinaryAvatarRepository(AvatarRepository nextRepository) {
29 | this.nextRepository = nextRepository;
30 | }
31 |
32 | @Override
33 | public String getLink(Profile profile, AvatarFormat format) {
34 | if (profile == null) {
35 | return nextRepository.getLink(profile, format);
36 | }
37 |
38 | String picture = profile.getPicture();
39 | if (!isCloudinaryLink(picture)) {
40 | return nextRepository.getLink(profile, format);
41 | }
42 |
43 | return configureLink(picture, format);
44 | }
45 |
46 | @Override
47 | public String getAnonymizedLink(String data) {
48 | return nextRepository.getAnonymizedLink(data);
49 | }
50 |
51 | @Override
52 | public boolean store(Profile profile, String url) {
53 | if (!isCloudinaryLink(url)) {
54 | return nextRepository.store(profile, url);
55 | }
56 |
57 | return applyChange(profile, url);
58 | }
59 |
60 | private boolean applyChange(Profile profile, String url) {
61 | profile.setPicture(url);
62 |
63 | return true;
64 | }
65 |
66 | private boolean isCloudinaryLink(String url) {
67 | return Strings.CS.startsWith(url, BASE_URL);
68 | }
69 |
70 | private String configureLink(String url, AvatarFormat format) {
71 | return url.replace("/upload/", "/upload/t_" + getTransformName(format) + "/");
72 | }
73 |
74 | private String getTransformName(AvatarFormat format) {
75 | return format.name().toLowerCase();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/GravatarAvatarRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import org.apache.commons.lang3.StringUtils;
22 |
23 | public class GravatarAvatarRepository implements AvatarRepository {
24 | private final GravatarAvatarGenerator gravatarAvatarGenerator;
25 | private final AvatarRepository nextRepository;
26 |
27 | public GravatarAvatarRepository(GravatarAvatarGenerator gravatarAvatarGenerator, AvatarRepository nextRepository) {
28 | this.gravatarAvatarGenerator = gravatarAvatarGenerator;
29 | this.nextRepository = nextRepository;
30 | }
31 |
32 | @Override
33 | public String getLink(Profile profile, AvatarFormat format) {
34 | if (profile == null) {
35 | return nextRepository.getLink(profile, format);
36 | }
37 |
38 | String picture = profile.getPicture();
39 | if (StringUtils.isBlank(picture)) {
40 | return gravatarAvatarGenerator.getLink(profile, format);
41 | }
42 |
43 | if (!gravatarAvatarGenerator.isLink(picture)) {
44 | return nextRepository.getLink(profile, format);
45 | }
46 |
47 | return gravatarAvatarGenerator.configureLink(picture, format);
48 | }
49 |
50 | @Override
51 | public String getAnonymizedLink(String data) {
52 | return gravatarAvatarGenerator.getRawLink(data);
53 | }
54 |
55 | @Override
56 | public boolean store(Profile profile, String url) {
57 | if (StringUtils.isBlank(url)) {
58 | String gravatarUrl = gravatarAvatarGenerator.getRawLink(profile);
59 | return applyChange(profile, gravatarUrl);
60 | }
61 |
62 | if (!gravatarAvatarGenerator.isLink(url)) {
63 | return nextRepository.store(profile, url);
64 | }
65 |
66 | return applyChange(profile, url);
67 | }
68 |
69 | private boolean applyChange(Profile profile, String url) {
70 | profile.setPicture(url);
71 | return true;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/controllers/ErrorController.java:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright 2013-2022 Erudika. https://erudika.com
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * For issues and patches go to: https://github.com/erudika
18 | */
19 | package com.erudika.scoold.controllers;
20 |
21 | import com.erudika.para.core.utils.ParaObjectUtils;
22 | import com.erudika.scoold.utils.ScooldUtils;
23 | import jakarta.inject.Inject;
24 | import jakarta.servlet.http.HttpServletRequest;
25 | import jakarta.servlet.http.HttpServletResponse;
26 | import java.io.IOException;
27 | import java.util.Collections;
28 | import org.apache.commons.lang3.Strings;
29 | import org.springframework.http.MediaType;
30 | import org.springframework.stereotype.Controller;
31 | import org.springframework.ui.Model;
32 | import org.springframework.web.bind.annotation.GetMapping;
33 | import org.springframework.web.bind.annotation.PathVariable;
34 |
35 | /**
36 | *
37 | * @author Alex Bogdanovski [alex@erudika.com]
38 | */
39 | @Controller
40 | public class ErrorController {
41 |
42 | private final ScooldUtils utils;
43 |
44 | @Inject
45 | public ErrorController(ScooldUtils utils) {
46 | this.utils = utils;
47 | }
48 |
49 | @GetMapping("/error/{code}")
50 | public String get(@PathVariable String code, HttpServletRequest req, HttpServletResponse res, Model model) throws IOException {
51 | model.addAttribute("path", "error.vm");
52 | model.addAttribute("title", utils.getLang(req).get("error.title"));
53 | model.addAttribute("status", req.getAttribute("jakarta.servlet.error.status_code"));
54 | model.addAttribute("reason", req.getAttribute("jakarta.servlet.error.message"));
55 | model.addAttribute("code", code);
56 |
57 | if (Strings.CS.startsWith((CharSequence) req.getAttribute("jakarta.servlet.forward.request_uri"), "/api/")) {
58 | res.setContentType(MediaType.APPLICATION_JSON_VALUE);
59 | ParaObjectUtils.getJsonWriterNoIdent().writeValue(res.getOutputStream(),
60 | Collections.singletonMap("error", code + " - " + req.getAttribute("jakarta.servlet.error.message")));
61 | }
62 | return "base";
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/avatars/AvatarRepositoryProxy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils.avatars;
19 |
20 | import com.erudika.scoold.core.Profile;
21 | import com.erudika.scoold.utils.ScooldUtils;
22 | import jakarta.inject.Singleton;
23 | import org.apache.commons.lang3.Strings;
24 | import org.springframework.stereotype.Component;
25 |
26 | @Component
27 | @Singleton
28 | public class AvatarRepositoryProxy implements AvatarRepository {
29 | private final AvatarRepository repository;
30 |
31 | public AvatarRepositoryProxy(GravatarAvatarGenerator gravatarAvatarGenerator) {
32 | this.repository = addGravatarIfEnabled(addCloudinaryIfEnabled(addImgurIfEnabled(getDefault())), gravatarAvatarGenerator);
33 | }
34 |
35 | private AvatarRepository addGravatarIfEnabled(AvatarRepository repo, GravatarAvatarGenerator gravatarAvatarGenerator) {
36 | return ScooldUtils.isGravatarEnabled() ? new GravatarAvatarRepository(gravatarAvatarGenerator, repo) : repo;
37 | }
38 |
39 | private AvatarRepository addImgurIfEnabled(AvatarRepository repo) {
40 | return ScooldUtils.isImgurAvatarRepositoryEnabled() ? new ImgurAvatarRepository(repo) : repo;
41 | }
42 |
43 | private AvatarRepository addCloudinaryIfEnabled(AvatarRepository repo) {
44 | return ScooldUtils.isCloudinaryAvatarRepositoryEnabled() ? new CloudinaryAvatarRepository(repo) : repo;
45 | }
46 |
47 | private AvatarRepository getDefault() {
48 | return new DefaultAvatarRepository();
49 | }
50 |
51 | @Override
52 | public String getLink(Profile profile, AvatarFormat format) {
53 | return repository.getLink(profile, format);
54 | }
55 |
56 | @Override
57 | public String getAnonymizedLink(String data) {
58 | return repository.getAnonymizedLink(data);
59 | }
60 |
61 | @Override
62 | public boolean store(Profile profile, String url) {
63 | if (profile != null && Strings.CS.equals(profile.getPicture(), url)) {
64 | return false;
65 | }
66 | return repository.store(profile, url);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/erudika/scoold/utils/RequestNotSupportedExceptionHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013-2022 Erudika. https://erudika.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * For issues and patches go to: https://github.com/erudika
17 | */
18 | package com.erudika.scoold.utils;
19 |
20 | import java.net.URI;
21 | import java.util.Set;
22 | import org.springframework.core.Ordered;
23 | import org.springframework.core.annotation.Order;
24 | import org.springframework.http.HttpHeaders;
25 | import org.springframework.http.HttpMethod;
26 | import org.springframework.http.HttpStatus;
27 | import org.springframework.http.HttpStatusCode;
28 | import org.springframework.http.ResponseEntity;
29 | import org.springframework.util.CollectionUtils;
30 | import org.springframework.web.HttpRequestMethodNotSupportedException;
31 | import org.springframework.web.bind.annotation.ControllerAdvice;
32 | import org.springframework.web.context.request.WebRequest;
33 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
34 | import org.springframework.web.servlet.resource.NoResourceFoundException;
35 |
36 | /**
37 | * HttpRequestMethodNotSupportedException handler - suppress spammy log messages.
38 | * @author Alex Bogdanovski [alex@erudika.com]
39 | */
40 | @Order(Ordered.HIGHEST_PRECEDENCE)
41 | @ControllerAdvice
42 | public class RequestNotSupportedExceptionHandler extends ResponseEntityExceptionHandler {
43 |
44 | @Override
45 | protected ResponseEntity
$!lang.get("comment.title")
4 | #end 5 | 6 | #if ($showComment) 7 | #commentbox($showComment) 8 | #elseif($commentslist && $commentslist.size() > 0) 9 | #paginate("comments" $itemcount "" "page" ) 10 | #elseif(!$scooldUtils.isAjaxRequest($request)) 11 | $!lang.get("search.notfound") 12 | #end 13 |