Cloud Native Impl

This commit is contained in:
Amit Kumar Nandi 2024-02-15 03:02:13 +05:30
parent ff528b7017
commit 16f4a85c40
58 changed files with 5834 additions and 321054 deletions

47
Dockerfile Normal file
View file

@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1
# Stage 1: Build the application
FROM maven:3.9.6-eclipse-temurin-21 AS builder
# Clone the repository
RUN git clone https://github.com/aamitn/URLShortener.git
WORKDIR /URLShortener
# Build the application
RUN mvn clean install
# Stage 2: Create the final image
FROM tomcat:10-jdk21-openjdk-slim
# Set environment variables
ENV CATALINA_BASE /usr/local/tomcat
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
# Copy the WAR file from the builder stage
COPY target/shortener.war $CATALINA_BASE/webapps/
# Add configuration for document base path
COPY server.xml $CATALINA_BASE/conf/server.xml
# Expose ports
EXPOSE 8080
EXPOSE 3306
# Copy the startup script
COPY shortener.sh /usr/local/tomcat/shortener.sh
# Copy the sql file
COPY create.sql /usr/local/tomcat/create.sql
# Grant execute permissions to the startup.sh script
RUN chmod +x /usr/local/tomcat/shortener.sh
# Start Tomcat and MariaDB using the startup script
CMD ["sh", "/usr/local/tomcat/shortener.sh"]

3
META-INF/MANIFEST.MF Normal file
View file

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.bitmutex.shortener.UrlShortenerApplication

File diff suppressed because one or more lines are too long

10
docker-compose.yml Normal file
View file

@ -0,0 +1,10 @@
version: '3.8'
services:
shortener-app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
- "3306:3306"

File diff suppressed because it is too large Load diff

Binary file not shown.

100
pom.xml
View file

@ -25,20 +25,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--QRCODE -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version> <!-- Replace with the latest version -->
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.4.1</version> <!-- Replace with the latest version -->
<version>3.4.1</version>
</dependency>
<!--QRCODE -->
<dependency>
@ -72,6 +69,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -82,7 +80,7 @@
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- or the latest version -->
<version>0.9.1</version>
</dependency>
<dependency>
@ -141,7 +139,7 @@
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
@ -157,23 +155,35 @@
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- Set packaging to WAR -->
<packaging>${project.packaging}</packaging>
<build>
<finalName>${artifactId}</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -183,7 +193,14 @@
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
<properties>
<property>
<name>surefire.testng.verbose</name>
<value>10</value>
</property>
</properties>
</configuration>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
@ -198,10 +215,20 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.3.RELEASE</version>
<version>3.2.2</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>pre-integration-test</id>
@ -217,6 +244,37 @@
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<project.packaging>jar</project.packaging>
</properties>
</profile>
<profile>
<id>release</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<project.packaging>war</project.packaging>
</properties>
</profile>
</profiles>
</project>

161
server.xml Normal file
View file

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!-- APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
HTTP Connector: /docs/config/http.html
AJP Connector: /docs/config/ajp.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true"
maxParameterCount="1000"
>
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443"
maxParameterCount="1000"
/>
-->
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" deployOnStartup="false">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<Context path="" docBase="shortener" debug="0" reloadable="true"></Context>
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

37
shortener.sh Normal file
View file

@ -0,0 +1,37 @@
#!/bin/bash
echo "Installing MariaDB..."
# Install MariaDB
apt-get update
apt-get install -y mariadb-server
echo "Starting MariaDB service..."
# Start MariaDB service
service mariadb start
echo "Waiting for MariaDB to start (adjust sleep time as needed)..."
# Wait for MariaDB to start
while ! mysqladmin ping -hlocalhost -uroot -p'YOUR_PASSWORD' --silent; do
echo "MariaDB is not yet available. Waiting..."
sleep 5
done
echo "Running SQL script to initialize the database..."
# Access MySQL Command Line and run SQL script to initialize the database
mysql -u root -e "source /usr/local/tomcat/create.sql"
echo "Displaying databases and tables..."
# Display the databases and tables
mysql -u root -e "SHOW DATABASES; USE shortener; SHOW TABLES;"
echo "Altering user and reloading privileges..."
# Run SQL commands to alter user and reload privileges
mysql -u root <<EOF
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('1234qwer');
FLUSH PRIVILEGES;
EOF
echo "Starting Tomcat in the background..."
# Start Tomcat in the background
sh /usr/local/tomcat/bin/catalina.sh run

File diff suppressed because it is too large Load diff

View file

@ -2,27 +2,26 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller
public class AnalyticsController {
@Autowired
private AnalyticsService analyticsService;
private final AnalyticsService analyticsService;
private AnalyticsRepository analyticsRepository;
@Autowired
private UrlShortenerService urlShortenerService;
private final UrlShortenerService urlShortenerService;
public AnalyticsController(AnalyticsService analyticsService, UrlShortenerService urlShortenerService) {
this.analyticsService = analyticsService;
this.urlShortenerService = urlShortenerService;
}
@GetMapping("/api/url/analytics")
@ResponseBody

View file

@ -1,7 +1,6 @@
package com.bitmutex.shortener;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;

View file

@ -2,18 +2,18 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class AnalyticsService {
@Autowired
private AnalyticsRepository analyticsRepository;
private final AnalyticsRepository analyticsRepository;
public AnalyticsService(AnalyticsRepository analyticsRepository) {
this.analyticsRepository = analyticsRepository;
}
public List<Analytics> findByUrlShortenerId(Long urlShortenerId) {
// Implement this method to fetch analytics entries based on urlShortenerId

View file

@ -5,9 +5,6 @@ package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Controller

View file

@ -1,13 +1,7 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@ -18,14 +12,18 @@ import java.io.IOException;
@RequestMapping("/contact")
public class ContactController {
@Autowired
final
ContactRepository contactRepository;
@Autowired
private SmsService smsService;
private final SmsService smsService;
@Autowired
private EmailService emailService;
private final EmailService emailService;
public ContactController(ContactRepository contactRepository, SmsService smsService, EmailService emailService) {
this.contactRepository = contactRepository;
this.smsService = smsService;
this.emailService = emailService;
}
@GetMapping
public String showContactForm() {

View file

@ -8,7 +8,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
public class CustomOAuth2User implements OAuth2User {
private OAuth2User oauth2User;
private final OAuth2User oauth2User;
public CustomOAuth2User(OAuth2User oauth2User) {
this.oauth2User = oauth2User;
@ -32,12 +32,12 @@ public class CustomOAuth2User implements OAuth2User {
// Additional methods to get custom user details
public String getEmail() {
return (String) oauth2User.getAttribute("email");
return oauth2User.getAttribute("email");
}
public String getFirstName() {
// Extract first name from GitHub attributes
return (String) oauth2User.getAttribute("name");
return oauth2User.getAttribute("name");
}
public String getLastName() {

View file

@ -1,6 +1,5 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
@ -15,12 +14,17 @@ import java.util.Random;
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Autowired
final
UserRepository userRepository;
@Autowired
final
UserService userService;
public CustomOAuth2UserService(UserRepository userRepository, UserService userService) {
this.userRepository = userRepository;
this.userService = userService;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(userRequest);
@ -79,8 +83,7 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserEntity registerNewOAuth2User(RegistrationRequest request) {
// Implement logic to register a new user based on OAuth2 attributes
UserEntity newUser = userService.registerNewUser(request);
return newUser;
return userService.registerNewUser(request);
}
public String randAlphaString() {
@ -89,12 +92,10 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService {
int targetStringLength = 10;
Random random = new Random();
String generatedString = random.ints(leftLimit, rightLimit + 1)
return random.ints(leftLimit, rightLimit + 1)
.filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97))
.limit(targetStringLength)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
return generatedString;
}
}

View file

@ -3,7 +3,6 @@ package com.bitmutex.shortener;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.Map;
public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
private final Long userId;

View file

@ -1,10 +1,7 @@
package com.bitmutex.shortener;
import com.nimbusds.openid.connect.sdk.assurance.evidences.attachment.Attachment;
import jakarta.mail.MessagingException;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
@ -13,12 +10,15 @@ import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
private final JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String senderEmail;
public EmailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void sendMail(String to, String subject, String message) {
try {
@ -40,7 +40,8 @@ public class EmailService {
javaMailSender.send(helper.getMimeMessage());
} catch (MessagingException e) {
// Handle email sending errors appropriately
e.printStackTrace();
throw new UrlShortenerException("Email Sending Error",e);
// e.printStackTrace();
}
}
}

View file

@ -11,8 +11,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.time.LocalDateTime;
import java.util.UUID;
@ -24,13 +22,13 @@ public class ForgotPasswordController {
private final UserService userService;
@Autowired
public ForgotPasswordController(UserService userService) {
public ForgotPasswordController(UserService userService, JavaMailSender javaMailSender) {
this.userService = userService;
this.javaMailSender = javaMailSender;
}
@Autowired
private JavaMailSender javaMailSender;
private final JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String senderEmail;

View file

@ -12,9 +12,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.LocalDateTime;
import java.util.UUID;
@Controller
@RequestMapping("/forgot-username")
public class ForgotUsernameController {
@ -22,13 +19,13 @@ public class ForgotUsernameController {
private final UserService userService;
@Autowired
public ForgotUsernameController(UserService userService) {
public ForgotUsernameController(UserService userService, JavaMailSender javaMailSender) {
this.userService = userService;
this.javaMailSender = javaMailSender;
}
@Autowired
private JavaMailSender javaMailSender;
private final JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String senderEmail;

View file

@ -1,15 +1,10 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.security.Principal;
import java.util.Optional;
@RequestMapping("/")
@ -17,8 +12,11 @@ import java.util.Optional;
public class HomeController {
@Autowired
private UserService userService;
private final UserService userService;
public HomeController(UserService userService) {
this.userService = userService;
}
@GetMapping("/")
String home(Authentication authentication, Model model) {

View file

@ -1,7 +1,6 @@
package com.bitmutex.shortener;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -16,9 +15,11 @@ import java.util.Map;
public class ImageController {
@Autowired
private ImageService imageService;
private final ImageService imageService;
public ImageController(ImageService imageService) {
this.imageService = imageService;
}
@PostMapping("/upload")
@ -51,7 +52,6 @@ public class ImageController {
@GetMapping("/{imageId}")
public ResponseEntity<byte[]> getImageById(@PathVariable Long imageId) {
ResponseEntity<byte[]> image = imageService.getImageContent(imageId);
return image;
return imageService.getImageContent(imageId);
}
}

View file

@ -1,6 +1,5 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -9,18 +8,19 @@ import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.InetAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Optional;
import java.util.Random;
@Service
public class ImageServiceImpl implements ImageService {
@Autowired
private ImageRepository imageRepository;
private final ImageRepository imageRepository;
public ImageServiceImpl(ImageRepository imageRepository) {
this.imageRepository = imageRepository;
}
@Override
public String uploadImage(MultipartFile file) {
@ -46,8 +46,7 @@ public class ImageServiceImpl implements ImageService {
return "/api/images/" + savedImage.getId();
} catch (IOException e) {
// Handle the exception (e.g., log or throw a custom exception)
e.printStackTrace();
return null;
throw new UrlShortenerException("Error uploading image",e);
}
}
@ -90,8 +89,7 @@ public class ImageServiceImpl implements ImageService {
return twelveDigitId;
} catch (NoSuchAlgorithmException e) {
// Handle the exception (e.g., log or throw a custom exception)
e.printStackTrace();
return 0;
throw new UrlShortenerException("Error generating unique ID for Image",e);
}
}
}

View file

@ -1,10 +1,8 @@
package com.bitmutex.shortener;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/")

View file

@ -1,6 +1,5 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.security.core.Authentication;
@ -8,8 +7,6 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Optional;
@Controller
public class MonitoringController {
private final HealthEndpoint healthEndpoint;

View file

@ -1,12 +1,7 @@
package com.bitmutex.shortener;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.transaction.Transactional;
import org.hibernate.annotations.GenericGenerator;
import java.util.UUID;
@Entity
public class OtpEntity {

View file

@ -1,7 +1,5 @@
package com.bitmutex.shortener;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.NoSuchElementException;
@ -10,9 +8,11 @@ import java.util.Optional;
@Service
public class OtpService {
@Autowired
private OtpRepository otpRepository;
private final OtpRepository otpRepository;
public OtpService(OtpRepository otpRepository) {
this.otpRepository = otpRepository;
}
// Generate and store OTP for a given username and phone number

View file

@ -1,41 +1,35 @@
package com.bitmutex.shortener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Optional;
@RequestMapping("/")
@Controller
public class ProfileController {
@Autowired
private UserService userService;
private final UserService userService;
@Autowired
private OtpService otpService;
private final OtpService otpService;
@Autowired
private SmsService smsService;
private final SmsService smsService;
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
public ProfileController(UserService userService, OtpService otpService, SmsService smsService, UserRepository userRepository) {
this.userService = userService;
this.otpService = otpService;
this.smsService = smsService;
this.userRepository = userRepository;
}
@GetMapping("/profile")
public String viewProfile(@RequestParam String username, Model model) {

View file

@ -1,6 +1,5 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -14,8 +13,11 @@ import java.util.Map;
@RequestMapping("/api/user/public/check")
public class PublicUserController {
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
public PublicUserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/username")
public ResponseEntity<Map<String, Boolean>> checkUsernameExists(@RequestParam String username) {

View file

@ -1,8 +1,5 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -17,12 +14,12 @@ import java.time.LocalDateTime;
@RequestMapping("/reset-password")
public class ResetPasswordController {
@Autowired
private UserService userService; // Assuming you have a UserService
private final UserService userService; // Assuming you have a UserService
public static PasswordEncoder passwordEncoder;
public ResetPasswordController(PasswordEncoder passwordEncoder) {
public ResetPasswordController(PasswordEncoder passwordEncoder, UserService userService) {
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
@GetMapping
@ -40,7 +37,7 @@ public class ResetPasswordController {
}
@PostMapping
public String processResetPassword(@RequestParam("token") String token, @RequestParam("password") String newPassword, Model model) {
public String processResetPassword(@RequestParam("token") String token, @RequestParam("password") String newPassword, Model ignoredModel) {
UserEntity user = userService.findByResetToken(token);
if (user != null && user.getResetTokenExpiryDateTime().isAfter(LocalDateTime.now())) {

View file

@ -1,13 +1,11 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@ -15,15 +13,17 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsServiceImpl userDetailsService;
private final UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
@ -33,7 +33,7 @@ public class SecurityConfig {
@Bean
public SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> securityConfigurerAdapter() {
return new SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
return new SecurityConfigurerAdapter<>() {
@Override
public void configure(HttpSecurity http) {
http.authenticationProvider(authenticationProvider());

View file

@ -1,24 +1,24 @@
package com.bitmutex.shortener;
import com.bitmutex.shortener.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/subscription")
public class SubscriptionController {
@Autowired
private SubscriptionService subscriptionService;
private final SubscriptionService subscriptionService;
@Autowired
private UserService userService;
private final UserService userService;
public SubscriptionController(SubscriptionService subscriptionService, UserService userService) {
this.subscriptionService = subscriptionService;
this.userService = userService;
}
@PostMapping("/change")
public ResponseEntity<?> changeSubscription(@RequestParam String username, @RequestParam String newPlanName) {

View file

@ -3,7 +3,5 @@ package com.bitmutex.shortener;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
// You can add custom queries or methods related to subscriptions here
// Custom query to find a subscription by user
Subscription findByUser(UserEntity user);
}

View file

@ -3,8 +3,6 @@ package com.bitmutex.shortener;
// UpdateUserRequest.java
import java.util.Optional;
public class UpdateUserRequest {
public String getUsername() {
return username;

View file

@ -1,6 +1,5 @@
package com.bitmutex.shortener;
import java.util.Base64;
import java.util.Date;
public class UrlDetailsDTO {

View file

@ -1,10 +1,11 @@
package com.bitmutex.shortener;
import jakarta.persistence.*;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -12,37 +13,36 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import jakarta.persistence.EntityManager;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Slf4j
@Controller
public class UrlRedirectionController {
@Autowired
private UrlShortenerRepository urlShortenerRepository;
private final UrlShortenerRepository urlShortenerRepository;
@Autowired
private AnalyticsRepository analyticsRepository;
private final AnalyticsRepository analyticsRepository;
@Autowired
private UrlShortenerService urlShortenerService;
private final UrlShortenerService urlShortenerService;
@PersistenceContext
private EntityManager entityManager;
@Autowired
private PasswordEncoder passwordEncoder;
private final PasswordEncoder passwordEncoder;
public UrlRedirectionController(UrlShortenerRepository urlShortenerRepository, AnalyticsRepository analyticsRepository, UrlShortenerService urlShortenerService, PasswordEncoder passwordEncoder) {
this.urlShortenerRepository = urlShortenerRepository;
this.analyticsRepository = analyticsRepository;
this.urlShortenerService = urlShortenerService;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("{shortUrl}")
public String submitPassword(@PathVariable String shortUrl,

View file

@ -3,13 +3,11 @@ package com.bitmutex.shortener;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import javax.imageio.ImageIO;
@ -19,8 +17,8 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.*;
@Entity
@Table(name = "url_shortener")
@ -203,8 +201,7 @@ public class UrlShortener {
return Base64.getEncoder().encodeToString(imageBytes);
} catch (Exception e) {
// Handle exceptions appropriately
e.printStackTrace();
return null;
throw new UrlShortenerException("Error generating B64QR for url",e);
}
}
private void addLogoToQrCode(BufferedImage qrImage, String logoPath) throws IOException {

View file

@ -1,13 +1,19 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
//@ComponentScan(basePackages = "com.bitmutex.shortener")
public class UrlShortenerApplication {
public class UrlShortenerApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(UrlShortenerApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
return builder.sources(UrlShortenerApplication.class);
}
}

View file

@ -2,27 +2,17 @@ package com.bitmutex.shortener;
import com.google.common.util.concurrent.RateLimiter;
import jakarta.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.bind.annotation.RequestBody;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@ -36,26 +26,28 @@ public class UrlShortenerController {
@Value("${server.servlet.context-path:}") // Inject the context path from application properties
private String contextPath;
@Autowired
private UrlShortenerService service;
private final UrlShortenerService service;
@Autowired
private RateLimiter rateLimiter;
private final RateLimiter rateLimiter;
@Autowired
private UrlShortenerRepository urlShortenerRepository;
private final UrlShortenerRepository urlShortenerRepository;
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
@Value("${cooldown.duration}")
private long cooldownSeconds;
@Autowired
private UserService userService;
private final UserService userService;
private final ConcurrentHashMap<String, Long> cooldownMap = new ConcurrentHashMap<>();
public UrlShortenerController(UrlShortenerService service, RateLimiter rateLimiter, UrlShortenerRepository urlShortenerRepository, UserRepository userRepository, UserService userService) {
this.service = service;
this.rateLimiter = rateLimiter;
this.urlShortenerRepository = urlShortenerRepository;
this.userRepository = userRepository;
this.userService = userService;
}
@PostMapping("/shorten")
@ -368,7 +360,7 @@ public class UrlShortenerController {
protected String getClientId() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String clientIp = request.getRemoteAddr();
return "ip_" + clientIp; // Prefixing with "ip_" for clarity
}

View file

@ -5,17 +5,18 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@ -30,17 +31,13 @@ import java.util.regex.Pattern;
@Slf4j
public class UrlShortenerService {
@Autowired
private UrlShortenerRepository repository;
private final UrlShortenerRepository repository;
@Autowired
private SubscriptionService subscriptionService;
private final SubscriptionService subscriptionService;
@Autowired
private UserService userService;
private final UserService userService;
@Autowired
private HttpServletRequest request; // Inject the HttpServletRequest
private final HttpServletRequest request; // Inject the HttpServletRequest
@Value("${server.port}") // Inject the server port from application properties
private String serverPort;
@ -51,8 +48,15 @@ public class UrlShortenerService {
@Value("${server.base.url}")
private String serverBaseUrl;
@Autowired
private PasswordEncoder passwordEncoder;
private final PasswordEncoder passwordEncoder;
public UrlShortenerService(UrlShortenerRepository repository, SubscriptionService subscriptionService, UserService userService, HttpServletRequest request, PasswordEncoder passwordEncoder) {
this.repository = repository;
this.subscriptionService = subscriptionService;
this.userService = userService;
this.request = request;
this.passwordEncoder = passwordEncoder;
}
public String shortenUrl(String jsonInput) {
try {
@ -79,9 +83,8 @@ public class UrlShortenerService {
String linkType = "short";
// Get the user entity (you need to implement a method to fetch the current user entity)
UserEntity userEntity = userService.findById(userId).get();
UserEntity userEntity = userService.findById(userId).orElseThrow(() -> new UsernameNotFoundException("User with ID: "+userId+" not found"));
// Check if the user has exceeded the maximum short URL limit
int maxShortUrlLimit = Integer.parseInt(subscriptionService.getCurrentSubscriptionDetails(userEntity).get("maxShortUrl").toString());
@ -205,12 +208,12 @@ public class UrlShortenerService {
}
// Regular expression for a simple URL format check
String urlRegex = "^(https?://)?([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})(/[a-zA-Z0-9-._?%&=]*)?$";
String urlRegex = "^(https?://)?([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})(/.*)?$";
try {
// Check if the URL has a scheme, if not, assume it is HTTP
if (!originalUrl.contains("://")) {
originalUrl = "http://" + originalUrl;
originalUrl = "https://" + originalUrl;
}
// Basic URL format check using regex
@ -289,7 +292,7 @@ public class UrlShortenerService {
Long userId = getCurrentUserId();
// Get the user entity (you need to implement a method to fetch the current user entity)
UserEntity userEntity = userService.findById(userId).get();
UserEntity userEntity = userService.findById(userId).orElseThrow(() -> new UsernameNotFoundException("User with ID: "+userId+" not found"));
// Check if the user has exceeded the bio pages limit
int currentBioPagesCount = repository.countBioPagesByUserId(userId);
@ -330,13 +333,9 @@ public class UrlShortenerService {
public List<UrlShortener> getUrlsByUserId(Long userId) {
try {
// Assume that UrlDetails is an entity representing the details of a URL
List<UrlShortener> urlDetailsList = repository.findByUserId(userId);
// You might need to convert entities to a DTO or customize the response based on your needs
// For simplicity, let's assume UrlDetails is the entity itself
return urlDetailsList;
// Assume that UrlDetails is an entity representing the details of a URL For simplicity, let's assume UrlDetails is the entity itself
// We might need to convert entities to a DTO or customize the response based on your needs
return repository.findByUserId(userId);
} catch (Exception e) {
// Handle exceptions or log them as needed
throw new UrlShortenerException("Error retrieving URLs by user ID", e);
@ -409,16 +408,30 @@ public class UrlShortenerService {
}
private boolean isValidHttpResponse(String originalUrl) {
private static boolean isValidHttpResponse(String originalUrl) {
try {
URL url = new URL(originalUrl);
final int CONNECT_TIMEOUT_MS = 1500; // 2 seconds
final int READ_TIMEOUT_MS = 1500; // 2 seconds
URI uri = URI.create(originalUrl);
URL url = uri.toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
connection.setReadTimeout(READ_TIMEOUT_MS);
connection.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
int responseCode = connection.getResponseCode();
return responseCode == HttpURLConnection.HTTP_OK;
System.out.println("URL VALIDATION RESPONSE CODE: " + responseCode + " for URL " + originalUrl);
// Consider both 200 (HTTP_OK) and 303 (HTTP_SEE_OTHER) as valid responses
return responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_SEE_OTHER;
} catch (IOException e) {
return false;
throw new UrlShortenerException("Validation Error : Bad Response",e);
}
}
}

View file

@ -1,15 +1,9 @@
package com.bitmutex.shortener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@ -21,14 +15,14 @@ import java.net.URL;
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
@Autowired
private OtpService otpService;
private final OtpService otpService;
@Autowired
private EmailService emailService;
private final EmailService emailService;
public UserController(UserService userService) {
public UserController(UserService userService, OtpService otpService, EmailService emailService) {
this.userService = userService;
this.otpService = otpService;
this.emailService = emailService;
}
@PostMapping("/register")
@ -103,8 +97,7 @@ public class UserController {
@GetMapping("/profile-picture")
public ResponseEntity<byte[]> getUserProfilePicture(@RequestParam String username) {
try {
ResponseEntity<byte[]> image = userService.getProfilePictureByUsername(username);
return image;
return userService.getProfilePictureByUsername(username);
} catch (UsernameNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} catch (Exception e) {

View file

@ -1,26 +1,21 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

View file

@ -3,8 +3,6 @@ package com.bitmutex.shortener;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Set;
@Entity
@Table(name = "user_entity")

View file

@ -1,10 +0,0 @@
package com.bitmutex.shortener;
import org.springframework.security.core.AuthenticationException;
public class UserNotVerifiedException extends AuthenticationException {
public UserNotVerifiedException(String msg) {
super(msg);
}
}

View file

@ -3,7 +3,6 @@ package com.bitmutex.shortener;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -18,19 +17,19 @@ import java.util.Optional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private SubscriptionService subscriptionService;
private final SubscriptionService subscriptionService;
@Autowired
private EmailService emailService;
private final EmailService emailService;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, SubscriptionService subscriptionService, EmailService emailService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.subscriptionService = subscriptionService;
this.emailService = emailService;
}

View file

@ -1,27 +1,29 @@
package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.swing.text.html.Option;
import java.util.Optional;
@Controller
public class VerificationController {
@Autowired
private UserService userService;
private final UserService userService;
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
@Autowired
private OtpService otpService;
private final OtpService otpService;
public VerificationController(UserService userService, UserRepository userRepository, OtpService otpService) {
this.userService = userService;
this.userRepository = userRepository;
this.otpService = otpService;
}
@GetMapping("/verify-registration")
@ -32,7 +34,7 @@ public class VerificationController {
@PostMapping("/verify-registration")
public ResponseEntity<String> verifyRegistration(@RequestParam String otp, @RequestParam String email, Model model) {
public ResponseEntity<String> verifyRegistration(@RequestParam String otp, @RequestParam String email, Model ignoredModel) {
//String email = model.getAttribute("email").toString();

View file

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.bitmutex.shortener.UrlShortenerApplication

View file

@ -6,7 +6,7 @@ database.ip=127.0.0.1
# DataSource settings
spring.datasource.url=jdbc:mysql://${database.ip}:${database.port}/${database.name}
spring.datasource.username=root
spring.datasource.password=
spring.datasource.password=1234qwer
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@ -22,7 +22,7 @@ cooldown.duration=30
requests.per.second=1
#Context Path(start with / and not end with /)
server.servlet.context-path=
server.servlet.context-path= /
#Server Port
server.port=8080
@ -37,7 +37,8 @@ spring.thymeleaf.cache=false
#logging level
logging.level.org.springframework.security: DEBUG
logging.level.org.springframework.security= INFO
logging.level.root=INFO
#SCHEMAGEN
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create

View file

@ -16,7 +16,7 @@
</appender>
<!-- Root logger -->
<root level="info">
<root level="debug">
<appender-ref ref="file"/>
</root>
</configuration>

View file

@ -22,9 +22,36 @@
<!-- Company Logo with SVG -->
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" class="w-8 h-8 mr-2">
<!-- Your SVG path here -->
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15h2v-6h-2zm0-8h2v-2h-2z" />
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- Background Parallelogram Shape -->
<polygon points="0,0 0,0, 120,60,0,170" fill="#3498db" />
<!-- Wire -->
<line x1="20" y1="50" x2="90" y2="50" stroke="#fff" stroke-width="5" />
<!-- Plug -->
<rect x="50" y="40" width="20" height="20" fill="#e74c3c" />
<!-- T Letter -->
<text x="2%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="52" letter-spacing="2">T</tspan>
</text>
<!-- U Letter -->
<text x="20%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">U</tspan>
</text>
<!-- S Letter -->
<text x="40%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">S</tspan>
</text>
<!-- C Letter -->
<text x="58%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">C</tspan>
</text>
</svg>
</div>

View file

@ -37,9 +37,36 @@
<!-- Company Logo with SVG -->
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" class="w-8 h-8 mr-2">
<!-- Your SVG path here -->
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15h2v-6h-2zm0-8h2v-2h-2z" />
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- Background Parallelogram Shape -->
<polygon points="0,0 0,0, 120,60,0,170" fill="#3498db" />
<!-- Wire -->
<line x1="20" y1="50" x2="90" y2="50" stroke="#fff" stroke-width="5" />
<!-- Plug -->
<rect x="50" y="40" width="20" height="20" fill="#e74c3c" />
<!-- T Letter -->
<text x="2%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="52" letter-spacing="2">T</tspan>
</text>
<!-- U Letter -->
<text x="20%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">U</tspan>
</text>
<!-- S Letter -->
<text x="40%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">S</tspan>
</text>
<!-- C Letter -->
<text x="58%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">C</tspan>
</text>
</svg>
</div>

View file

@ -119,5 +119,4 @@
});
</script>
</body>
<div th:replace="layouts/footerLayout"></div>
</html>

View file

@ -2,9 +2,41 @@
<div class="mx-auto w-full max-w-screen-xl p-4 py-6 lg:py-8">
<div class="md:flex md:justify-between">
<div class="mb-6 md:mb-0">
<a href="https://flowbite.com/" class="flex items-center">
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span>
<a href="/" class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- Background Parallelogram Shape -->
<polygon points="0,0 0,0, 120,60,0,170" fill="#3498db" />
<!-- Wire -->
<line x1="20" y1="50" x2="90" y2="50" stroke="#fff" stroke-width="5" />
<!-- Plug -->
<rect x="50" y="40" width="20" height="20" fill="#e74c3c" />
<!-- T Letter -->
<text x="2%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="52" letter-spacing="2">T</tspan>
</text>
<!-- U Letter -->
<text x="20%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">U</tspan>
</text>
<!-- S Letter -->
<text x="40%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">S</tspan>
</text>
<!-- C Letter -->
<text x="58%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">C</tspan>
</text>
</svg>
<h5>The Url Shortener Company</h5>
</a>
</div>
<div class="grid grid-cols-2 gap-8 sm:gap-6 sm:grid-cols-3">
@ -12,10 +44,10 @@
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Resources</h2>
<ul class="text-gray-500 dark:text-gray-400 font-medium">
<li class="mb-4">
<a href="https://flowbite.com/" class="hover:underline">Flowbite</a>
<a href="/docs-ui" class="hover:underline">API Docs</a>
</li><br>
<li>
<a href="https://tailwindcss.com/" class="hover:underline">Tailwind CSS</a>
<a href="/docs-ui" class="hover:underline">JAR/WAR Artifact</a>
</li>
</ul>
</div>
@ -23,7 +55,7 @@
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Follow us</h2>
<ul class="text-gray-500 dark:text-gray-400 font-medium">
<li class="mb-4">
<a href="https://github.com/themesberg/flowbite" class="hover:underline ">Github</a>
<a href="https://github.com/aamitn/URLShortener" class="hover:underline ">Github</a>
</li><br>
<li>
<a href="https://discord.gg/4eeurUVvTy" class="hover:underline">Discord</a>
@ -45,7 +77,7 @@
</div>
<hr class="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" />
<div class="sm:flex sm:items-center sm:justify-between">
<span class="text-sm text-gray-500 sm:text-center dark:text-gray-400">© 2023 <a href="https://flowbite.com/" class="hover:underline">Flowbite™</a>. All Rights Reserved.
<span class="text-sm text-gray-500 sm:text-center dark:text-gray-400">© 2024 <a href="https://bitmutex.com/" class="hover:underline">Bitmutex Technologies</a>. All Rights Reserved.
</span>
<div class="flex mt-4 sm:justify-center sm:mt-0">
<a href="#" class="text-gray-500 hover:text-gray-900 dark:hover:text-white">

View file

@ -2,11 +2,41 @@
<div id="navbar" class="flex flex-wrap bg-gray-800 text-white py-4 px-8 mx-auto">
<div class="flex items-center mb-4 w-full md:w-auto md:mr-auto">
<!-- Company Logo with SVG -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" class="w-8 h-8 mr-2">
<!-- Your SVG path here -->
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15h2v-6h-2zm0-8h2v-2h-2z" />
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- Background Parallelogram Shape -->
<polygon points="0,0 0,0, 120,60,0,170" fill="#3498db" />
<!-- Wire -->
<line x1="20" y1="50" x2="90" y2="50" stroke="#fff" stroke-width="5" />
<!-- Plug -->
<rect x="50" y="40" width="20" height="20" fill="#e74c3c" />
<!-- T Letter -->
<text x="2%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="52" letter-spacing="2">T</tspan>
</text>
<!-- U Letter -->
<text x="20%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">U</tspan>
</text>
<!-- S Letter -->
<text x="40%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">S</tspan>
</text>
<!-- C Letter -->
<text x="58%" y="70%" font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="25" fill="#fff" font-weight="bold">
<tspan font-family="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif" font-size="40" letter-spacing="2">C</tspan>
</text>
</svg>
</div>
<ul class="flex flex-wrap justify-start space-x-4 items-center w-full md:w-auto">

View file

@ -18,18 +18,20 @@
<div class="mb-8">
<ul class="flex border-b">
<li class="-mb-px mr-1">
<a href="javascript:void(0)" onclick="openTab(event, 'createBio')" class="bg-white inline-block py-2 px-4 font-semibold">Create Bio</a>
<a href="javascript:void(0)" onclick="openTab(event, 'createBio')" class="bg-white inline-block py-2 px-4 font-semibold"><i class="fa fa-user"></i> Create Bio</a>
</li>
<li class="-mb-px mr-1">
<a href="javascript:void(0)" onclick="openTab(event, 'shortenURL')" class="bg-white inline-block py-2 px-4 font-semibold">Shorten URL</a>
<a href="javascript:void(0)" onclick="openTab(event, 'shortenURL')" class="bg-white inline-block py-2 px-4 font-semibold"><i class="fa fa-link"></i> Shorten URL</a>
</li>
</ul>
<div id="createBio" class="tab-content">
<div class="bg-white p-6 rounded-lg shadow-md mt-4">
<h2 class="text-xl font-bold mb-4">Create Bio Form</h2>
<h2 class="text-xl font-bold mb-4">Create Your own customized bio page</h2>
<!-- Bio creation form goes here -->
<form>
<form onsubmit="window.location.href='/login'; return false;">
<textarea id="htmlData" rows="2" class="block mx-4 p-2.5 w-3/4 text-sm text-gray-900 bg-white rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Your Bio Page Content..." ></textarea>
<p></p>
<!-- Bio form fields go here -->
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full mt-4">Create Bio</button>
</form>
@ -38,9 +40,15 @@
<div id="shortenURL" class="hidden tab-content">
<div class="bg-white p-6 rounded-lg shadow-md mt-4">
<h2 class="text-xl font-bold mb-4">Shorten URL Form</h2>
<h2 class="text-xl font-bold mb-4">Shorten your long url</h2>
<!-- URL shortening form goes here -->
<form>
<form onsubmit="window.location.href='/login'; return false;">
<input type="text" id="originalUrl" placeholder="Enter the original URL"
class="px-4 py-2 border border-gray-300 rounded focus:outline-none">
<p></p>
<input type="text" id="backHalf" placeholder="Custom Backhalf"
class="px-4 py-2 border border-gray-300 rounded focus:outline-none">
<p></p>
<!-- URL form fields go here -->
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full mt-4">Shorten URL</button>
</form>
@ -55,18 +63,28 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Feature Card 1 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 1</h3>
<p class="text-gray-600">Description of feature 1.</p>
<h3 class="text-xl font-bold mb-2"><i class="fas fa-link"></i> Custom Short URLs</h3>
<p class="text-gray-600">Generate custom short URLs that reflect your brand or content.</p>
</div>
<!-- Feature Card 2 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 2</h3>
<p class="text-gray-600">Description of feature 2.</p>
<h3 class="text-xl font-bold mb-2"><i class="fas fa-lock"></i> Password Protection</h3>
<p class="text-gray-600">Secure your short URLs with password protection, ensuring access only to authorized users.</p>
</div>
<!-- Feature Card 3 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 3</h3>
<p class="text-gray-600">Description of feature 3.</p>
<h3 class="text-xl font-bold mb-2"><i class="fas fa-toggle-on"></i> Link State Management</h3>
<p class="text-gray-600">Control the state of your short URLs, enabling or disabling them based on your preferences.</p>
</div>
<!-- Feature Card 4 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2"><i class="fas fa-user-circle"></i> Bio Pages</h3>
<p class="text-gray-600">Create personalized bio pages to share additional information about yourself or your business.</p>
</div>
<!-- Feature Card 5 (Add more features as needed) -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2"><i class="fas fa-chart-bar"></i> Detailed Analytics</h3>
<p class="text-gray-600">Access detailed analytics for each shortened URL, including click counts and user data.</p>
</div>
</div>
</div>
@ -75,92 +93,109 @@
<div class="mb-8">
<h2 class="text-3xl font-bold mb-4 text-center">Pricing</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Pricing Card 1 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Basic Plan</h3>
<p class="text-gray-600">$19.99/month</p>
<!-- Free Plan -->
<div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<h3 class="text-2xl font-bold mb-4">Free Plan</h3>
<p class="text-gray-600">10 Short URLs</p>
<p class="text-gray-600">5 Bio Pages</p>
<p class="text-gray-600">Price: $0/month</p>
</div>
<!-- Pricing Card 2 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Pro Plan</h3>
<p class="text-gray-600">$39.99/month</p>
<!-- Basic Plan -->
<div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<h3 class="text-2xl font-bold mb-4">Basic Plan</h3>
<p class="text-gray-600">50 Short URLs</p>
<p class="text-gray-600">20 Bio Pages</p>
<p class="text-gray-600">Price: INR 89/month</p>
</div>
<!-- Pricing Card 3 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Enterprise Plan</h3>
<p class="text-gray-600">Contact us for a quote</p>
<!-- Pro Plan -->
<div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<h3 class="text-2xl font-bold mb-4">Pro Plan</h3>
<p class="text-gray-600">100 Short URLs</p>
<p class="text-gray-600">50 Bio Pages</p>
<p class="text-gray-600">Price: INR 129/month</p>
</div>
<!-- Enterprise Plan -->
<div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<h3 class="text-2xl font-bold mb-4">Enterprise Plan</h3>
<p class="text-gray-600">Unlimited Short URLs</p>
<p class="text-gray-600">Unlimited Bio Pages</p>
<p class="text-gray-600">Contact us for pricing</p>
<a href="/contact" class="text-blue-500 font-semibold">Contact Us</a>
</div>
</div>
</div>
<!-- Flowbite Accordion Section -->
<!-- URL Shortener FAQ Section -->
<div class="bg-white rounded-lg overflow-hidden shadow-md">
<div id="accordion-open" data-accordion="open">
<!-- FAQ Item 1 -->
<h2 id="accordion-open-heading-1">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 rounded-t-xl focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-1" aria-expanded="true" aria-controls="accordion-open-body-1">
<div id="accordion-open" data-accordion="open">
<!-- FAQ Item 1 -->
<h2 id="accordion-open-heading-1">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 rounded-t-xl focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-1" aria-expanded="true" aria-controls="accordion-open-body-1">
<span class="flex items-center">
<svg class="w-5 h-5 me-2 shrink-0" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path>
</svg> What is Flowbite?
</svg> What is our URL Shortener service?
</span>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-1" class="hidden" aria-labelledby="accordion-open-heading-1">
<div class="p-5 border border-b-0 border-gray-200 dark:border-gray-700 dark:bg-gray-900">
<p class="mb-2 text-gray-500 dark:text-gray-400">Flowbite is an open-source library of interactive components built on top of Tailwind CSS, including buttons, dropdowns, modals, navbars, and more.</p>
<p class="text-gray-500 dark:text-gray-400">Check out this guide to learn how to <a href="/docs/getting-started/introduction/" class="text-blue-600 dark:text-blue-500 hover:underline">get started</a> and start developing websites even faster with components on top of Tailwind CSS.</p>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-1" class="hidden" aria-labelledby="accordion-open-heading-1">
<div class="p-5 border border-b-0 border-gray-200 dark:border-gray-700 dark:bg-gray-900">
<p class="mb-2 text-gray-500 dark:text-gray-400">Our URL Shortener service is a powerful tool that allows you to create short and shareable links. Whether you're looking to track clicks, manage branded links, or enhance user experience, our service has you covered.</p>
<p class="text-gray-500 dark:text-gray-400">Check out our <a href="/docs-ui" class="text-blue-600 dark:text-blue-500 hover:underline">documentation</a> to learn how to get started with our URL Shortener service.</p>
</div>
</div>
</div>
<!-- FAQ Item 2 -->
<h2 id="accordion-open-heading-2">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-2" aria-expanded="false" aria-controls="accordion-open-body-2">
<!-- FAQ Item 2 -->
<h2 id="accordion-open-heading-2">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-2" aria-expanded="false" aria-controls="accordion-open-body-2">
<span class="flex items-center">
<svg class="w-5 h-5 me-2 shrink-0" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path>
</svg> Is there a Figma file available?
</svg> Can I customize my short URLs?
</span>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-2" class="hidden" aria-labelledby="accordion-open-heading-2">
<div class="p-5 border border-b-0 border-gray-200 dark:border-gray-700">
<p class="mb-2 text-gray-500 dark:text-gray-400">Flowbite is first conceptualized and designed using the Figma software so everything you see in the library has a design equivalent in our Figma file.</p>
<p class="text-gray-500 dark:text-gray-400">Check out the <a href="https://flowbite.com/figma/" class="text-blue-600 dark:text-blue-500 hover:underline">Figma design system</a> based on the utility classes from Tailwind CSS and components from Flowbite.</p>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-2" class="hidden" aria-labelledby="accordion-open-heading-2">
<div class="p-5 border border-b-0 border-gray-200 dark:border-gray-700">
<p class="mb-2 text-gray-500 dark:text-gray-400">Absolutely! With our URL Shortener service, you have the flexibility to customize your short URLs to reflect your brand or campaign. Add your own keywords or choose a custom alias to make your links more recognizable and meaningful.</p>
<p class="text-gray-500 dark:text-gray-400">Learn how to create custom short URLs in our <a href="https://your-url-shortener-docs.com/customization" class="text-blue-600 dark:text-blue-500 hover:underline">customization guide</a>.</p>
</div>
</div>
</div>
<!-- FAQ Item 3 -->
<h2 id="accordion-open-heading-3">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-3" aria-expanded="false" aria-controls="accordion-open-body-3">
<!-- FAQ Item 3 -->
<h2 id="accordion-open-heading-3">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3" data-accordion-target="#accordion-open-body-3" aria-expanded="false" aria-controls="accordion-open-body-3">
<span class="flex items-center">
<svg class="w-5 h-5 me-2 shrink-0" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path>
</svg> What are the differences between Flowbite and Tailwind UI?
</svg> How can I track the performance of my short URLs?
</span>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-3" class="hidden" aria-labelledby="accordion-open-heading-3">
<div class="p-5 border border-t-0 border-gray-200 dark:border-gray-700">
<p class="mb-2 text-gray-500 dark:text-gray-400">The main difference is that the core components from Flowbite are open source under the MIT license, whereas Tailwind UI is a paid product. Another difference is that Flowbite relies on smaller and standalone components, whereas Tailwind UI offers sections of pages.</p>
<p class="mb-2 text-gray-500 dark:text-gray-400">However, we actually recommend using both Flowbite, Flowbite Pro, and even Tailwind UI as there is no technical reason stopping you from using the best of two worlds.</p>
<p class="mb-2 text-gray-500 dark:text-gray-400">Learn more about these technologies:</p>
<ul class="ps-5 text-gray-500 list-disc dark:text-gray-400">
<li><a href="https://flowbite.com/pro/" class="text-blue-600 dark:text-blue-500 hover:underline">Flowbite Pro</a></li>
<li><a href="https://tailwindui.com/" rel="nofollow" class="text-blue-600 dark:text-blue-500 hover:underline">Tailwind UI</a></li>
</ul>
<svg data-accordion-icon class="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg>
</button>
</h2>
<div id="accordion-open-body-3" class="hidden" aria-labelledby="accordion-open-heading-3">
<div class="p-5 border border-t-0 border-gray-200 dark:border-gray-700">
<p class="mb-2 text-gray-500 dark:text-gray-400">Our URL Shortener service provides comprehensive analytics to help you track the performance of your short URLs. Monitor the number of clicks, geographic location of users, and referral sources in real-time.</p>
<p class="mb-2 text-gray-500 dark:text-gray-400">Explore the detailed analytics dashboard in our <a href="https://your-url-shortener-dashboard.com" class="text-blue-600 dark:text-blue-500 hover:underline">dashboard guide</a> to gain valuable insights into your link engagement.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Call to Action Section -->
<div class="bg-gray-800 text-white p-8 rounded-lg shadow-md text-center">
<h2 class="text-2xl font-bold mb-4">Ready to get started?</h2>

View file

@ -102,53 +102,61 @@
<!-- Add more sections for other Actuator endpoints as needed -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Fetch health data using JS fetch
fetch('/actuator/health')
.then(response => response.json())
.then(data => {
// Update the content of the healthDataContainer with the fetched data
document.getElementById('healthDataContainer').innerText = JSON.stringify(data, null, 2);
//Function to Fetch health data using JS fetch
function fetchHealthData() {
fetch('/actuator/health')
.then(response => response.json())
.then(data => {
// Update the content of the healthDataContainer with the fetched data
document.getElementById('healthDataContainer').innerText = JSON.stringify(data, null, 2);
// Parse JSON data
var json = JSON.parse(JSON.stringify(data));
// Parse JSON data
var json = JSON.parse(JSON.stringify(data));
// Update Component Status
updateComponentStatus('app', json.components && json.components.custom);
updateComponentStatus('db', json.components && json.components.db);
updateComponentStatus('diskSpace', json.components && json.components.diskSpace);
updateComponentStatus('mail', json.components && json.components.mail);
updateComponentStatus('ping', json.components && json.components.ping);
// Update Component Status
updateComponentStatus('app', json.components && json.components.custom);
updateComponentStatus('db', json.components && json.components.db);
updateComponentStatus('diskSpace', json.components && json.components.diskSpace);
updateComponentStatus('mail', json.components && json.components.mail);
updateComponentStatus('ping', json.components && json.components.ping);
// Extract disk space information
const diskSpace = json.components && json.components.diskSpace;
// Extract disk space information
const diskSpace = json.components && json.components.diskSpace;
// Prepare data for pie chart
if (diskSpace && diskSpace.status === 'UP' && diskSpace.details) {
const free = diskSpace.details.free;
const total = diskSpace.details.total;
// Prepare data for pie chart
if (diskSpace && diskSpace.status === 'UP' && diskSpace.details) {
const free = diskSpace.details.free;
const total = diskSpace.details.total;
console.log(free/100000);
console.log(total/100000);
console.log(free / 100000);
console.log(total / 100000);
// Create a disk space pie chart using Chart.js
const ctx = document.getElementById('diskSpaceChart').getContext('2d');
const diskSpaceChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Total Space', 'Free Space'],
datasets: [{
data: [total, free],
backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)'],
borderWidth: 2
}]
}
});
} else {
console.error('Error: Disk space data not available or status is not UP.');
}
})
.catch(error => console.error('Error fetching health data:', error));
// Create a disk space pie chart using Chart.js
const ctx = document.getElementById('diskSpaceChart').getContext('2d');
const diskSpaceChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Total Space', 'Free Space'],
datasets: [{
data: [total, free],
backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)'],
borderWidth: 2
}]
}
});
} else {
console.error('Error: Disk space data not available or status is not UP.');
}
})
.catch(error => console.error('Error fetching health data:', error));
}
// Fetch data initially when the page loads
fetchHealthData();
// Fetch data every 2 seconds (adjust the interval as needed)
const fetchInterval = 2000; // 5 seconds
setInterval(fetchHealthData, fetchInterval);
// Function to update component status
function updateComponentStatus(componentId, component) {
@ -172,7 +180,7 @@
}
</script>
</div>
<div th:replace="layouts/footerLayout"></div>
</body>
</html>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>TUSC - The URL Shortener Company</display-name>
</web-app>