Skip to content

Commit

Permalink
Added rating and captcha service
Browse files Browse the repository at this point in the history
  • Loading branch information
gdevxy committed Dec 28, 2024
1 parent d15b6c8 commit 27e995b
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 43 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
env:
CONTENTFUL_CDA_TOKEN: ${{ secrets.CONTENTFUL_CDA_TOKEN }}
CONTENTFUL_CMA_TOKEN: ${{ secrets.CONTENTFUL_CMA_TOKEN }}
CONTENTFUL_CPA_TOKEN: ${{ secrets.CONTENTFUL_CPA_TOKEN }}
GRAVATAR_API_KEY: ${{ secrets.GRAVATAR_API_KEY }}

strategy:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.gdevxy.blog.client.google;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

import com.gdevxy.blog.client.google.model.CaptchaVerifyRequest;
import com.gdevxy.blog.client.google.model.CaptchaVerifyResponse;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(configKey = "google")
public interface GoogleClient {

@POST
@Path("/recaptcha/api/siteverify")
Uni<CaptchaVerifyResponse> verifyCaptcha(CaptchaVerifyRequest req);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gdevxy.blog.client.google.model;

public record CaptchaVerifyRequest(String secret, String response) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.gdevxy.blog.client.google.model;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;

@Getter
@Builder
@Jacksonized
@ToString
public class CaptchaVerifyResponse {

@Builder.Default
private final Boolean success = false;
@JsonProperty("error-codes")
private final List<String> errorCodes;
private final String hostname;

}
7 changes: 7 additions & 0 deletions client/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ quarkus.rest-client.gravatar.url=https://api.gravatar.com
quarkus.rest-client.gravatar.headers.AUTHORIZATION=Bearer ${GRAVATAR_API_KEY}
quarkus.rest-client.gravatar.read-timeout=2000

quarkus.rest-client.google.url=https://www.google.com
quarkus.rest-client.google.read-timeout=3000

quarkus.rest-client.contentful-cma.url=https://api.contentful.com/spaces/r9z7bjp5iedy/environments/master
quarkus.rest-client.contentful-cma.headers.AUTHORIZATION=Bearer ${CONTENTFUL_CMA_TOKEN}
quarkus.rest-client.contentful-cma.read-timeout=2000

#quarkus.rest-client.logging.scope=request-response
#quarkus.rest-client.logging.body-limit=2048
#quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG

# GraphQL
quarkus.smallrye-graphql-client.default.url=https://graphql.contentful.com/content/v1/spaces/r9z7bjp5iedy/environments/master
quarkus.smallrye-graphql-client.contentful.url=${quarkus.smallrye-graphql-client.default.url}
Expand Down
1 change: 1 addition & 0 deletions component/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<groupId>org.webjars.npm</groupId>
<artifactId>bootstrap-icons</artifactId>
<version>${bootstrap-icons.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

import com.gdevxy.blog.model.BlogPost;
import com.gdevxy.blog.model.BlogPostRateReq;
import com.gdevxy.blog.model.Image;
import com.gdevxy.blog.model.contentful.Node;
import com.gdevxy.blog.service.contentful.ContentfulAssetService;
Expand Down Expand Up @@ -52,10 +52,10 @@ public Uni<TemplateInstance> blogPost(@Valid @Size(max = 255) @PathParam("slug")
}

@POST
@Path("/{id}")
public Uni<Void> rate(@Valid @Size(max = 22) @PathParam("id") String id) {
@Path("/{id}/rate")
public Uni<Void> rate(@Valid @Size(max = 22) @PathParam("id") String id, @Valid BlogPostRateReq req) {

return blogPostService.rate(id);
return blogPostService.rate(id, req.captcha());
}

@CheckedTemplate
Expand Down
121 changes: 85 additions & 36 deletions component/src/main/resources/templates/BlogPostResource/blogPost.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,45 @@
{#pageDescription}{blogPost.seo.description}{/pageDescription}
{#pageRobots}{blogPost.seo.robotHint}{/pageRobots}

{#extraJs}
{#extraCss}
<link rel="stylesheet" href="/webjars/highlightjs/styles/stackoverflow-dark.min.css"/>
{/extraCss}

{#extraJs}
<script src="https://www.google.com/recaptcha/api.js?render=6LeWR6gqAAAAAC4uiRPFjYaOaVLrTXh4m94Wbgs0"></script>
<script src="/webjars/highlightjs/highlight.min.js"></script>
<script src="/webjars/highlightjs/languages/java.min.js"></script>
<script src="/webjars/highlightjs/languages/json.min.js"></script>
<script src="/webjars/highlightjs/languages/xml.min.js"></script>
<script>hljs.highlightAll();</script>
<script src="/_static/jquery/dist/jquery.min.js"></script>
<script>
$(document).ready(function () {
$("#btnRate").on('click', function () {
grecaptcha.ready(function () {
grecaptcha
.execute("6LeWR6gqAAAAAC4uiRPFjYaOaVLrTXh4m94Wbgs0", { action: "rate_post" })
.then(function (token) {
$.ajax({
type: 'POST',
url: `/blog-posts/{blogPost.id}/rate`,
dataType: 'json',
contentType: "application/json",
data: JSON.stringify({
captcha: token
}),
success: function () {
$(`#btnRate`).addClass("text-neon").addClass("pulse");
},
error: function (err) {
$(`#btnRate`).addClass("tada");
}
});
});
});
});
});
</script>
{/extraJs}

{#extraBundle}
Expand All @@ -22,7 +54,7 @@

{#withProgressBar}{/withProgressBar}

<div class="mb-5 pb-5 rounded-top bg-body-tertiary">
<div class="mb-5 rounded-top bg-body-tertiary">

<div class="card background-purple rounded-top" style="border: none; border-radius: unset">
<div class="row g-0">
Expand Down Expand Up @@ -53,46 +85,63 @@ <h1 class="card-title">{blogPost.title}</h1>
{/for}
</div>
</div>
<div class="col col-xl-10">
<div class="col-xl-10">
{#else}
<div class="col">
{/if}
{#for block in blogPost.blocks}
{#when block.node}
{#is EMBEDDED_ENTRY}
{#include blog/blog-image imageId=block.value images=images /}
{#is HEADING_1}
{#include blog/blog-header heading=1 value=block.blocks[0] /}
{#is HEADING_2}
{#include blog/blog-header heading=2 value=block.blocks[0] /}
{#is HEADING_3}
{#include blog/blog-header heading=3 value=block.blocks[0] /}
{#is HEADING_4}
{#include blog/blog-header heading=4 value=block.blocks[0] /}
{#is HEADING_5}
{#include blog/blog-header heading=5 value=block.blocks[0] /}
{#is HEADING_6}
{#include blog/blog-header heading=6 value=block.blocks[0] /}
{#is PARAGRAPH}
{#include blog/blog-paragraph blocks=block.blocks /}
{#is UL_LIST}
{#include blog/blog-unordered-list blocks=block.blocks /}
{#else}
{/when}
{/for}
</div>
{#for block in blogPost.blocks}
{#when block.node}
{#is EMBEDDED_ENTRY}
{#include blog/blog-image imageId=block.value images=images /}
{#is HEADING_1}
{#include blog/blog-header heading=1 value=block.blocks[0] /}
{#is HEADING_2}
{#include blog/blog-header heading=2 value=block.blocks[0] /}
{#is HEADING_3}
{#include blog/blog-header heading=3 value=block.blocks[0] /}
{#is HEADING_4}
{#include blog/blog-header heading=4 value=block.blocks[0] /}
{#is HEADING_5}
{#include blog/blog-header heading=5 value=block.blocks[0] /}
{#is HEADING_6}
{#include blog/blog-header heading=6 value=block.blocks[0] /}
{#is PARAGRAPH}
{#include blog/blog-paragraph blocks=block.blocks /}
{#is UL_LIST}
{#include blog/blog-unordered-list blocks=block.blocks /}
{#else}
{/when}
{/for}
</div>
</div>
{#if blogPost.relatedBlogPosts}
<div class="row g-0">
<div class="col-12">
<span class="fst-italic">Related blog posts</span>
<hr />
{#for relatedBlogPost in blogPost.relatedBlogPosts}
{#if relatedBlogPost_isFirst}/{/if}&nbsp;<a href="/blog-posts/{relatedBlogPost.slug}" class="text-decoration-none link-opacity-100 link-opacity-75-hover">{relatedBlogPost.title}</a>&nbsp;/
{/for}

<div class="row g-0 mt-1 bg-body-secondary">
{#if blogPost.withIndexHeading}
<div class="col-2 d-none d-xl-flex flex-column">
&nbsp;
</div>
<div class="col-xl-10 text-end">
{#else}
<div class="col text-end">
{/if}
<div class="blog-paragraph">
Leave a thumbs up if you liked this post <span class="ps-2"><button id="btnRate" type="button" class="btn btn-dark bi bi-hand-thumbs-up-fill"></button></span>
</div>
</div>
{/if}
</div>

</div>

{#if blogPost.relatedBlogPosts}
<div class="row g-0">
<div class="col-12">
<span class="fst-italic">Related blog posts</span>
<hr />
{#for relatedBlogPost in blogPost.relatedBlogPosts}
{#if relatedBlogPost_isFirst}/{/if}&nbsp;<a href="/blog-posts/{relatedBlogPost.slug}" class="text-decoration-none link-opacity-100 link-opacity-75-hover">{relatedBlogPost.title}</a>&nbsp;/
{/for}
</div>
</div>
{/if}

{/include}
43 changes: 43 additions & 0 deletions component/src/main/resources/web/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
color: var(--bs-color-neon) !important;
}

.grecaptcha-badge {
visibility: hidden;
}

.pb-index a {
color: var(--bs-index-color);
}
Expand Down Expand Up @@ -139,3 +143,42 @@
.text-string {
color: #F08D49
}

.tada {
animation: tada 1s 1;
}

@keyframes tada {
0% {
transform: scale3d(1, 1, 1);
}

10%, 20% {
transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
}

30%, 50%, 70%, 90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}

40%, 60%, 80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}

100% {
transform: scale3d(1, 1, 1);
}
}

.pulse {
animation: pulse-animation 2s infinite;
}

@keyframes pulse-animation {
0% {
box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2);
}
100% {
box-shadow: 0 0 0 20px rgba(0, 0, 0, 0);
}
}
2 changes: 2 additions & 0 deletions doc/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ kubectl create secret docker-registry docker-password --docker-server={domain} -

```script
kubectl create secret generic blog-secrets \
--from-literal=GDEVXY_DATABASE_USERNAME=<secret> \
--from-literal=GDEVXY_DATABASE_PASSWORD=<secret> \
--from-literal=GRAVATAR_API_KEY=<secret> \
--from-literal=CONTENTFUL_CDA_TOKEN=<secret> \
--from-literal=CONTENTFUL_CMA_TOKEN=<secret> \
Expand Down
4 changes: 4 additions & 0 deletions model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<groupId>io.quarkus.qute</groupId>
<artifactId>qute-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gdevxy.blog.model;

import jakarta.validation.constraints.NotEmpty;

public record BlogPostRateReq(@NotEmpty String captcha) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.gdevxy.blog.service.captcha;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.ForbiddenException;

import com.gdevxy.blog.client.google.GoogleClient;
import com.gdevxy.blog.client.google.model.CaptchaVerifyRequest;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;

@Slf4j
@ApplicationScoped
public class CaptchaService {

@RestClient
GoogleClient googleClient;

@ConfigProperty(name = "google.captcha.secret")
String secret;

public Uni<Void> verify(String captcha) {

return googleClient.verifyCaptcha(new CaptchaVerifyRequest(secret, captcha))
.invoke(Unchecked.consumer(res -> {
if (!res.getSuccess()){
log.info("Captcha validation failed {}", res);
throw new ForbiddenException();
}
}))
.replaceWithVoid();
}

}
Loading

0 comments on commit 27e995b

Please sign in to comment.