diff --git a/src/main/frontend/src/app/components/mobiletags.js b/src/main/frontend/src/app/components/mobiletags.js
new file mode 100644
index 0000000..8d27c59
--- /dev/null
+++ b/src/main/frontend/src/app/components/mobiletags.js
@@ -0,0 +1,17 @@
+import React, {Component} from "react";
+import ApiCall from "../services/api-call";
+import Tags from "./tags";
+
+export default class MobileTags extends Tags {
+ render() {
+ return (
+ this.state.tags.map((tag, i) => {
+ let link = "/" + tag.name;
+ return
this.handleLinkClick(e, link)}>{tag.name}
+
+ })
+ );
+ }
+}
+
diff --git a/src/main/frontend/src/app/components/sponsor-edit.js b/src/main/frontend/src/app/components/sponsor-edit.js
new file mode 100644
index 0000000..43b314b
--- /dev/null
+++ b/src/main/frontend/src/app/components/sponsor-edit.js
@@ -0,0 +1,151 @@
+import React, {Component} from "react";
+import ApiCall from "../services/api-call";
+
+export default class SponsorEdit extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ sponsor: "",
+ web_url: "",
+ logo: null,
+ image_type: "",
+ message: null,
+ preview: null,
+ };
+
+ this.onSubmit = this.onSubmit.bind(this);
+ // this.goToRegister = this.goToRegister.bind(this);
+ }
+
+ onChange = e => this.setState({[e.target.name]: e.target.value});
+
+ onFileChangeHandler = (e) => {
+ this.setState({
+ logo: e.target.files[0],
+ image_type: e.target.files[0].type,
+ preview: URL.createObjectURL(e.target.files[0])
+ });
+ };
+
+ componentWillReceiveProps() {
+ const sponsorId = this.props.routeParams.sponsorId;
+ if (sponsorId === 'new-sponsor') {
+ this.setState({
+ id: null,
+ sponsor: "",
+ web_url: "",
+ logo: null,
+ image_type: "",
+ preview: null,
+ })
+ } else {
+ ApiCall.get("/api/sponsor/" + sponsorId)
+ .then((response) => this.setState(
+ {
+ id: response.data.id,
+ sponsor: response.data.name,
+ web_url: response.data.url,
+ preview: "/api/sponsor/logo/" + response.data.id
+ }
+ ));
+ }
+ }
+
+ onSubmit(event) {
+ event.preventDefault();
+
+ const formData = new FormData();
+ if (this.state.id) {
+ formData.append("id", this.state.id)
+ }
+ formData.append("sponsor", this.state.sponsor);
+ formData.append("web_url", this.state.web_url);
+ if (this.state.logo) {
+ formData.append("image_type", this.state.image_type);
+ formData.append("logo", this.state.logo);
+ }
+
+ if (!(this.state.sponsor && this.state.web_url)) {
+ this.setState({message: "Please fill all fields."});
+ } else {
+ let self = this;
+ //update sponsor
+ if (this.state.id) {
+ ApiCall.put("/api/sponsor", formData)
+ .then((response) => {
+ console.log(response.headers);
+ self.setState({message: "Sponsor with ID: " + this.state.id + " updated!"});
+ self.props.router.push("/");
+ })
+ .catch(function (error) {
+ // handle error
+ console.log(error);
+ self.setState({message: "Ops... nothing happened (check the browser console)." + error.response.status});
+ });
+ } else {
+ //create sponsor
+ ApiCall.post("/api/sponsor", formData)
+ .then((response) => {
+ console.log(response.headers);
+ self.setState({message: "New Sponsor Created!"});
+ self.props.router.push("/");
+ })
+ .catch(function (error) {
+ // handle error
+ console.log(error);
+ self.setState({message: "Ops... nothing happened (check the browser console)." + error.response.status});
+ });
+ }
+ }
+ }
+
+ render() {
+
+ let error = null;
+
+ if (this.state.message) {
+ error = (
+
+ {this.state.message}
+
);
+ }
+
+ return (
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/frontend/src/app/components/sponsor.js b/src/main/frontend/src/app/components/sponsor.js
new file mode 100644
index 0000000..6961543
--- /dev/null
+++ b/src/main/frontend/src/app/components/sponsor.js
@@ -0,0 +1,49 @@
+import React, {Component} from "react";
+import JwtUtil from "../services/jwt-util";
+
+export default class Sponsor extends Component {
+ constructor(props) {
+ super(props);
+ this.hashHistory = this.props.hashHistory;
+ }
+
+ componentDidMount() {
+ }
+
+ handleSponsorEdit(event, id) {
+ window.scrollTo({
+ top: 0,
+ left: 0,
+ behavior: 'smooth'
+ });
+ this.hashHistory.push("/edit-sponsor/" + id);
+ }
+
+ handleSponsorRemove(event, id) {
+ // this.props.router.push("/remove-sponsor/" + id);
+ }
+
+ render() {
+ let cursorPointerStyle = {
+ cursor: "pointer"
+ };
+
+ let editSponsor = JwtUtil.isCurrentUserAdmin() ?
+ (
+ this.handleSponsorEdit(e, this.props.sponsor.id)}>Edit
+ this.handleSponsorRemove(e, this.props.sponsor.id)}>Delete
+ ) : '';
+
+ return (
+
+
+

+ {editSponsor}
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/frontend/src/app/components/sponsors.js b/src/main/frontend/src/app/components/sponsors.js
new file mode 100644
index 0000000..c8cf829
--- /dev/null
+++ b/src/main/frontend/src/app/components/sponsors.js
@@ -0,0 +1,83 @@
+import React, {Component} from "react";
+import ApiCall from "../services/api-call";
+import JwtUtil from "../services/jwt-util";
+import Sponsor from "./sponsor";
+
+export default class Sponsors extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {sponsors: []};
+ this.hashHistory = this.props.hashHistory;
+ }
+
+ loadSingleSponsorById = (id) => {
+ ApiCall.get("/api/sponsor/" + id)
+ .then((response) => this.setState(
+ {
+ sponsors: [response.data]
+ }
+ ));
+ };
+
+ loadSponsors = () => {
+ ApiCall.get("/api/sponsor")
+ .then((response) => this.setState(
+ {
+ sponsors: response.data
+ }
+ ));
+ };
+
+ componentWillMount() {
+ this.loadSponsors();
+ }
+
+ handlerSponsorAdd(event) {
+ window.scrollTo({
+ top: 0,
+ left: 0,
+ behavior: 'smooth'
+ });
+ this.hashHistory.push("/add-sponsor/new-sponsor");
+ }
+
+ render() {
+ let cursorPointerStyle = {
+ cursor: "pointer"
+ };
+
+ let sponsors = this.state.sponsors.map((sponsor, i) => {
+ return
+ });
+
+ let idx = 0;
+ let sponsor_rows = [];
+ let i;
+ for (i = 0; i < sponsors.length; i += 5) {
+ sponsor_rows[idx++] =
+ {sponsors.slice(i, Math.max(i + 5, sponsors.length))}
+
+ }
+
+ return (
+
+ {JwtUtil.isCurrentUserAdmin() ?
+
+
+
this.handlerSponsorAdd(e)}>Add Sponsor
+
+ : ''
+ }
+
+ {sponsor_rows}
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/frontend/src/app/components/tags.js b/src/main/frontend/src/app/components/tags.js
index 078d05d..fa4a85c 100644
--- a/src/main/frontend/src/app/components/tags.js
+++ b/src/main/frontend/src/app/components/tags.js
@@ -13,7 +13,7 @@ export default class Tags extends Component {
// })};
}
- componentWillMount() {
+ componentDidMount() {
let self = this;
let articles = ApiCall.get("/api/tag")
.then((response) => this.setState({tags: response.data}))
diff --git a/src/main/frontend/src/app/services/api-call.js b/src/main/frontend/src/app/services/api-call.js
index 8a17bf3..e017f3c 100644
--- a/src/main/frontend/src/app/services/api-call.js
+++ b/src/main/frontend/src/app/services/api-call.js
@@ -36,7 +36,7 @@ export default class ApiCall {
static getRestUrl() {
if (!baseUrl) {
- baseUrl = "http://localhost:8080";
+ baseUrl = window.location.origin;
}
return baseUrl;
diff --git a/src/main/frontend/src/index.js b/src/main/frontend/src/index.js
index 69b3cd6..d05020f 100644
--- a/src/main/frontend/src/index.js
+++ b/src/main/frontend/src/index.js
@@ -1,7 +1,8 @@
import React from "react";
import ReactDOM from "react-dom";
-import Articles from "./app/components/articles";
+import Articles, {articles} from "./app/components/articles";
import Tags from "./app/components/tags";
+import MobileTags from "./app/components/mobiletags";
import {hashHistory, Route, Router} from 'react-router'
import TagsFooter from "./app/components/tags-footer";
import CfpSubmit from "./app/components/cfp";
@@ -9,12 +10,17 @@ import Login from "./app/components/login";
import Registration from "./app/components/registration";
import SidebarPosts from "./app/components/sidebar-posts";
import ArticleEdit from "./app/components/article-edit";
+import SponsorEdit from "./app/components/sponsor-edit";
+import Sponsors from "./app/components/sponsors";
ReactDOM.render(
, document.querySelector('#tags'));
+ReactDOM.render(
+
+ , document.querySelector('.slicknav_nav'));
const Routing = () => (
@@ -26,6 +32,8 @@ const Routing = () => (
+
+
);
@@ -35,5 +43,7 @@ ReactDOM.render(, document
ReactDOM.render(, document.querySelector('#tab2'));
+ReactDOM.render(, document.querySelector('#sponsors_list'));
+
ReactDOM.render(, document.querySelector('#footer-menu'));
diff --git a/src/main/java/bg/jug/website/cms/service/EventService.java b/src/main/java/bg/jug/website/cms/service/EventService.java
index 17d9786..543333c 100644
--- a/src/main/java/bg/jug/website/cms/service/EventService.java
+++ b/src/main/java/bg/jug/website/cms/service/EventService.java
@@ -53,13 +53,13 @@ public Response updateEvent(@Valid Event event) {
if (persisted == null) {
return Response.status(Response.Status.NOT_FOUND).build();
- } else {
- EntityUtils.updateEntity(persisted, event);
- replaceTagsWithExistingOnes(event);
- //Eager fetching. Otherwise page will not serialize
- persisted.getTags().size();
- return Response.ok(persisted).build();
}
+
+ EntityUtils.updateEntity(persisted, event);
+ replaceTagsWithExistingOnes(event);
+ //Eager fetching. Otherwise page will not serialize
+ persisted.getTags().size();
+ return Response.ok(persisted).build();
}
@DELETE
diff --git a/src/main/java/bg/jug/website/cms/service/TagAwareService.java b/src/main/java/bg/jug/website/cms/service/TagAwareService.java
index 430b95e..3401a82 100644
--- a/src/main/java/bg/jug/website/cms/service/TagAwareService.java
+++ b/src/main/java/bg/jug/website/cms/service/TagAwareService.java
@@ -13,22 +13,26 @@
* Base class for services dealing with tag relations
*/
public class TagAwareService {
+
protected void replaceTagsWithExistingOnes(Article article) {
- if (article.getTags() != null && !article.getTags().isEmpty()) {
- Set tagsToPersist = new HashSet<>();
- article.getTags()
- .forEach(possiblyNewTag ->
- {
- List existingTags = Tag.find(Tag.FIND_BY_NAME, possiblyNewTag.getName()).page(
- Page.of(0, 1)).list();
- if(existingTags != null && !existingTags.isEmpty()) {
- Tag existingTag = existingTags.get(0);
- tagsToPersist.add(existingTag);
- } else {
- tagsToPersist.add(possiblyNewTag);
- }
- });
- article.setTags(tagsToPersist);
+ if (article.getTags() == null || article.getTags().isEmpty()) {
+ return;
+ }
+
+ Set tagsToPersist = new HashSet<>();
+ article.getTags().forEach(possiblyNewTag -> replaceTag(tagsToPersist, possiblyNewTag));
+ article.setTags(tagsToPersist);
+ }
+
+ private void replaceTag(Set tagsToPersist, Tag possiblyNewTag) {
+ List existingTags =
+ Tag.find(Tag.FIND_BY_NAME, possiblyNewTag.getName()).page(Page.of(0, 1)).list();
+
+ if (existingTags != null && !existingTags.isEmpty()) {
+ Tag existingTag = existingTags.get(0);
+ tagsToPersist.add(existingTag);
+ } else {
+ tagsToPersist.add(possiblyNewTag);
}
}
}
diff --git a/src/main/java/bg/jug/website/core/util/CryptUtils.java b/src/main/java/bg/jug/website/core/util/CryptUtils.java
index 7629ede..17e0abd 100644
--- a/src/main/java/bg/jug/website/core/util/CryptUtils.java
+++ b/src/main/java/bg/jug/website/core/util/CryptUtils.java
@@ -2,21 +2,22 @@
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.security.Key;
public class CryptUtils {
private static final String KEY = "lkjq9q91jaq*9!l#";
- public static String encryptPassword(String password) {
+ public static byte[] encryptPassword(String password) {
Key aesKey = new SecretKeySpec(KEY.getBytes(), "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
- byte[] encrypted = cipher.doFinal(password.getBytes());
- return new String(encrypted);
+ return cipher.doFinal(password.getBytes());
} catch (Exception e) {
- return password;
+ return password.getBytes(StandardCharsets.UTF_8);
}
}
diff --git a/src/main/java/bg/jug/website/sponsors/model/Sponsor.java b/src/main/java/bg/jug/website/sponsors/model/Sponsor.java
new file mode 100644
index 0000000..b6d7373
--- /dev/null
+++ b/src/main/java/bg/jug/website/sponsors/model/Sponsor.java
@@ -0,0 +1,65 @@
+package bg.jug.website.sponsors.model;
+
+import javax.json.bind.annotation.JsonbTransient;
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import bg.jug.website.core.model.AbstractEntity;
+
+@Entity
+public class Sponsor extends AbstractEntity {
+
+ @NotNull
+ @Size(min = 1, max = 150)
+ @Column(length = 150)
+ private String name;
+
+ @NotNull
+ @Size(min = 1, max = 200)
+ @Column(length = 200)
+ private String url;
+
+ @Column(length = 100)
+ @JsonbTransient
+ private String type;
+
+ @JsonbTransient
+ @Lob
+ private byte[] logo;
+
+ public Sponsor() {
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public byte[] getLogo() {
+ return logo;
+ }
+
+ public void setLogo(byte[] logo) {
+ this.logo = logo;
+ }
+}
diff --git a/src/main/java/bg/jug/website/sponsors/service/SponsorService.java b/src/main/java/bg/jug/website/sponsors/service/SponsorService.java
new file mode 100644
index 0000000..9d219a1
--- /dev/null
+++ b/src/main/java/bg/jug/website/sponsors/service/SponsorService.java
@@ -0,0 +1,119 @@
+package bg.jug.website.sponsors.service;
+
+import javax.annotation.security.RolesAllowed;
+import javax.enterprise.context.RequestScoped;
+import javax.transaction.Transactional;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.*;
+import java.util.List;
+import java.util.Map;
+
+import bg.jug.website.sponsors.model.Sponsor;
+import org.jboss.resteasy.plugins.providers.multipart.InputPart;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+
+@RequestScoped
+@Path("/sponsor")
+@Produces(MediaType.APPLICATION_JSON)
+public class SponsorService {
+
+ @POST
+ @RolesAllowed("admin")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Transactional
+ public Response createSponsor(MultipartFormDataInput input) {
+ Sponsor sponsor = new Sponsor();
+
+ try {
+ updateSponsorFieldsFromInputData(input, sponsor);
+ } catch (IOException e) {
+ return Response.serverError().entity(e.getMessage()).build();
+ }
+ sponsor.persistAndFlush();
+
+ return Response.ok().build();
+ }
+
+ private void updateSponsorFieldsFromInputData(MultipartFormDataInput input, Sponsor sponsor)
+ throws IOException {
+ Map> paramsMap = input.getFormDataMap();
+ String name = paramsMap.get("sponsor").get(0).getBodyAsString();
+ String url = paramsMap.get("web_url").get(0).getBodyAsString();
+ sponsor.setName(name);
+ sponsor.setUrl(url);
+
+ if (paramsMap.containsKey("image_type")) {
+ fillSponsorImageFields(sponsor, paramsMap);
+ }
+ }
+
+ private void fillSponsorImageFields(Sponsor sponsor, Map> paramsMap)
+ throws IOException {
+
+ String type = paramsMap.get("image_type").get(0).getBodyAsString();
+ InputStream logoStream = paramsMap.get("logo").get(0).getBody(InputStream.class, null);
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[1024];
+
+ int length = logoStream.read(buffer);
+ while (length > 0) {
+ baos.write(buffer, 0, length);
+ length = logoStream.read(buffer);
+ }
+ sponsor.setType(type);
+ sponsor.setLogo(baos.toByteArray());
+ }
+ }
+
+ @PUT
+ @RolesAllowed("admin")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Transactional
+ public Response updateSponsor(MultipartFormDataInput input) {
+ Map> paramsMap = input.getFormDataMap();
+
+ if (!paramsMap.containsKey("id")) {
+ return Response.status(BAD_REQUEST).entity("missing ID").build();
+ }
+
+ try {
+ long id = paramsMap.get("id").get(0).getBody(Long.class, null);
+ Sponsor sponsor = Sponsor.findById(id);
+ if (sponsor == null) {
+ return Response.status(NOT_FOUND).build();
+ }
+
+ updateSponsorFieldsFromInputData(input, sponsor);
+ } catch (IOException e) {
+ Response.serverError().entity(e.getMessage()).build();
+ }
+
+ return Response.ok().build();
+ }
+
+ @GET
+ public Response allSponsors() {
+ List allSubmissions = Sponsor.findAll().list();
+ return Response.ok(allSubmissions).build();
+ }
+
+ @GET
+ @Path("{id}")
+ public Response sponsor(@PathParam("id") long id) {
+ Sponsor sponsor = Sponsor.findById(id);
+ return Response.ok().entity(sponsor).build();
+ }
+
+ @GET
+ @Path("/logo/{id}")
+ public Response sponsorLogo(@PathParam("id") long id) {
+ Sponsor sponsor = Sponsor.findById(id);
+ return Response.ok().type(sponsor.getType()).entity(sponsor.getLogo()).build();
+ }
+}
diff --git a/src/main/java/bg/jug/website/user/model/User.java b/src/main/java/bg/jug/website/user/model/User.java
index 4b63888..8ed6035 100644
--- a/src/main/java/bg/jug/website/user/model/User.java
+++ b/src/main/java/bg/jug/website/user/model/User.java
@@ -6,9 +6,7 @@
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Lob;
-import java.util.Collections;
-import java.util.List;
-
+import java.util.*;
@Entity
public class User extends AbstractEntity {
@@ -30,7 +28,7 @@ public class User extends AbstractEntity {
private String bio;
@JsonbTransient
- private String password;
+ private byte[] password;
@JsonbTransient
private String salt;
@@ -42,13 +40,13 @@ public class User extends AbstractEntity {
public User() {
}
- public User(String email, String password, String salt) {
+ public User(String email, byte[] password, String salt) {
this(null, null, email, null, null, password, salt,
- Collections.singletonList(DEFAULT_ROLE));
+ new ArrayList<>(Collections.singletonList(DEFAULT_ROLE)));
}
public User(String nickname, String fullname, String email, byte[] photo,
- String bio, String password, String salt, List roles) {
+ String bio, byte[] password, String salt, List roles) {
this.nickname = nickname;
this.fullname = fullname;
this.email = email;
@@ -89,10 +87,10 @@ public String getBio() {
public void setBio(String bio) {
this.bio = bio;
}
- public String getPassword() {
+ public byte[] getPassword() {
return password;
}
- public void setPassword(String password) {
+ public void setPassword(byte[] password) {
this.password = password;
}
public String getSalt() {
diff --git a/src/main/java/bg/jug/website/user/service/UserService.java b/src/main/java/bg/jug/website/user/service/UserService.java
index 9058bcd..9115e94 100644
--- a/src/main/java/bg/jug/website/user/service/UserService.java
+++ b/src/main/java/bg/jug/website/user/service/UserService.java
@@ -18,6 +18,7 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.Arrays;
@RequestScoped
@Path("/user")
@@ -33,9 +34,13 @@ public class UserService {
@Transactional
public Response registerUser(@Valid LoginDetails registrationDetails) {
// TODO Check if email is already registered
+
String salt = RandomStringUtils.randomAlphanumeric(20);
- String encrypted = CryptUtils.encryptPassword(registrationDetails.getPassword() + salt);
+ byte[] encrypted = CryptUtils.encryptPassword(registrationDetails.getPassword() + salt);
User newUser = new User(registrationDetails.getEmail(), encrypted, salt);
+ if (User.findAll().count() == 0) {
+ newUser.getRoles().add("admin");
+ }
newUser.persist();
return Response.ok().header("Authorization", getJwt(newUser)).build();
@@ -51,8 +56,8 @@ public Response loginUser(LoginDetails loginDetails) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
- String encrypted = CryptUtils.encryptPassword(loginDetails.getPassword() + user.getSalt());
- if (!user.getPassword().equals(encrypted)) {
+ byte[] encrypted = CryptUtils.encryptPassword(loginDetails.getPassword() + user.getSalt());
+ if (!Arrays.equals(user.getPassword(), encrypted)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
diff --git a/src/main/resources/META-INF/resources/img/sponsors/418.png b/src/main/resources/META-INF/resources/img/sponsors/418.png
new file mode 100644
index 0000000..50dfa36
Binary files /dev/null and b/src/main/resources/META-INF/resources/img/sponsors/418.png differ
diff --git a/src/main/resources/META-INF/resources/img/sponsors/99.png b/src/main/resources/META-INF/resources/img/sponsors/99.png
new file mode 100644
index 0000000..cb8ffe7
Binary files /dev/null and b/src/main/resources/META-INF/resources/img/sponsors/99.png differ
diff --git a/src/main/resources/META-INF/resources/index.html b/src/main/resources/META-INF/resources/index.html
index 54f0954..9903163 100755
--- a/src/main/resources/META-INF/resources/index.html
+++ b/src/main/resources/META-INF/resources/index.html
@@ -38,11 +38,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -182,8 +184,8 @@
-
BGJUG
- Bulgarian Java User Group
+ JProfessionals conference
+ Organised by Bulgarian Java User group
@@ -281,413 +283,304 @@ Margaret Gould