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> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId> <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--QRCODE --> <!--QRCODE -->
<dependency> <dependency>
<groupId>com.google.zxing</groupId> <groupId>com.google.zxing</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>3.4.1</version> <!-- Replace with the latest version --> <version>3.4.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.zxing</groupId> <groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId> <artifactId>javase</artifactId>
<version>3.4.1</version> <!-- Replace with the latest version --> <version>3.4.1</version>
</dependency> </dependency>
<!--QRCODE --> <!--QRCODE -->
<dependency> <dependency>
@ -72,6 +69,7 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
@ -82,7 +80,7 @@
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- or the latest version --> <version>0.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
@ -141,7 +139,7 @@
<dependency> <dependency>
<groupId>org.testng</groupId> <groupId>org.testng</groupId>
<artifactId>testng</artifactId> <artifactId>testng</artifactId>
<version>7.4.0</version> <version>7.9.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@ -157,23 +155,35 @@
<version>2.1.0</version> <version>2.1.0</version>
</dependency> </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> </dependencies>
<!-- Set packaging to WAR -->
<packaging>${project.packaging}</packaging>
<build> <build>
<finalName>${artifactId}</finalName>
<pluginManagement>
<plugins> <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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -183,7 +193,14 @@
<suiteXmlFiles> <suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile> <suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles> </suiteXmlFiles>
<properties>
<property>
<name>surefire.testng.verbose</name>
<value>10</value>
</property>
</properties>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>
@ -198,10 +215,20 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <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> <executions>
<execution> <execution>
<id>pre-integration-test</id> <id>pre-integration-test</id>
@ -217,6 +244,37 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement>
</build> </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> </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; package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller @Controller
public class AnalyticsController { public class AnalyticsController {
@Autowired private final AnalyticsService analyticsService;
private AnalyticsService analyticsService;
private AnalyticsRepository analyticsRepository; private AnalyticsRepository analyticsRepository;
@Autowired private final UrlShortenerService urlShortenerService;
private UrlShortenerService urlShortenerService;
public AnalyticsController(AnalyticsService analyticsService, UrlShortenerService urlShortenerService) {
this.analyticsService = analyticsService;
this.urlShortenerService = urlShortenerService;
}
@GetMapping("/api/url/analytics") @GetMapping("/api/url/analytics")
@ResponseBody @ResponseBody

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,7 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import com.nimbusds.openid.connect.sdk.assurance.evidences.attachment.Attachment;
import jakarta.mail.MessagingException; 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.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
@ -13,12 +10,15 @@ import org.springframework.stereotype.Service;
@Service @Service
public class EmailService { public class EmailService {
@Autowired private final JavaMailSender javaMailSender;
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}") @Value("${spring.mail.username}")
private String senderEmail; private String senderEmail;
public EmailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void sendMail(String to, String subject, String message) { public void sendMail(String to, String subject, String message) {
try { try {
@ -40,7 +40,8 @@ public class EmailService {
javaMailSender.send(helper.getMimeMessage()); javaMailSender.send(helper.getMimeMessage());
} catch (MessagingException e) { } catch (MessagingException e) {
// Handle email sending errors appropriately // 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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
@ -24,13 +22,13 @@ public class ForgotPasswordController {
private final UserService userService; private final UserService userService;
@Autowired @Autowired
public ForgotPasswordController(UserService userService) { public ForgotPasswordController(UserService userService, JavaMailSender javaMailSender) {
this.userService = userService; this.userService = userService;
this.javaMailSender = javaMailSender;
} }
@Autowired private final JavaMailSender javaMailSender;
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}") @Value("${spring.mail.username}")
private String senderEmail; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import java.time.LocalDateTime;
import java.util.UUID;
@Controller @Controller
@RequestMapping("/forgot-username") @RequestMapping("/forgot-username")
public class ForgotUsernameController { public class ForgotUsernameController {
@ -22,13 +19,13 @@ public class ForgotUsernameController {
private final UserService userService; private final UserService userService;
@Autowired @Autowired
public ForgotUsernameController(UserService userService) { public ForgotUsernameController(UserService userService, JavaMailSender javaMailSender) {
this.userService = userService; this.userService = userService;
this.javaMailSender = javaMailSender;
} }
@Autowired private final JavaMailSender javaMailSender;
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}") @Value("${spring.mail.username}")
private String senderEmail; private String senderEmail;

View file

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

View file

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

View file

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

View file

@ -1,10 +1,8 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller @Controller
@RequestMapping("/") @RequestMapping("/")

View file

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

View file

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

View file

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

View file

@ -1,41 +1,35 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired; 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.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; 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 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("/") @RequestMapping("/")
@Controller @Controller
public class ProfileController { public class ProfileController {
@Autowired private final UserService userService;
private UserService userService;
@Autowired private final OtpService otpService;
private OtpService otpService;
@Autowired private final SmsService smsService;
private SmsService smsService;
@Autowired private final UserRepository userRepository;
private 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") @GetMapping("/profile")
public String viewProfile(@RequestParam String username, Model model) { public String viewProfile(@RequestParam String username, Model model) {

View file

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

View file

@ -1,8 +1,5 @@
package com.bitmutex.shortener; 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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -17,12 +14,12 @@ import java.time.LocalDateTime;
@RequestMapping("/reset-password") @RequestMapping("/reset-password")
public class ResetPasswordController { public class ResetPasswordController {
@Autowired private final UserService userService; // Assuming you have a UserService
private UserService userService; // Assuming you have a UserService
public static PasswordEncoder passwordEncoder; public static PasswordEncoder passwordEncoder;
public ResetPasswordController(PasswordEncoder passwordEncoder) { public ResetPasswordController(PasswordEncoder passwordEncoder, UserService userService) {
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
this.userService = userService;
} }
@GetMapping @GetMapping
@ -40,7 +37,7 @@ public class ResetPasswordController {
} }
@PostMapping @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); UserEntity user = userService.findByResetToken(token);
if (user != null && user.getResetTokenExpiryDateTime().isAfter(LocalDateTime.now())) { if (user != null && user.getResetTokenExpiryDateTime().isAfter(LocalDateTime.now())) {

View file

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

View file

@ -1,24 +1,24 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import com.bitmutex.shortener.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map; import java.util.Map;
import java.util.Optional;
@RestController @RestController
@RequestMapping("/api/subscription") @RequestMapping("/api/subscription")
public class SubscriptionController { public class SubscriptionController {
@Autowired private final SubscriptionService subscriptionService;
private SubscriptionService subscriptionService;
@Autowired private final UserService userService;
private UserService userService;
public SubscriptionController(SubscriptionService subscriptionService, UserService userService) {
this.subscriptionService = subscriptionService;
this.userService = userService;
}
@PostMapping("/change") @PostMapping("/change")
public ResponseEntity<?> changeSubscription(@RequestParam String username, @RequestParam String newPlanName) { 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; import org.springframework.data.jpa.repository.JpaRepository;
public interface SubscriptionRepository extends JpaRepository<Subscription, Long> { 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); Subscription findByUser(UserEntity user);
} }

View file

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

View file

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

View file

@ -1,10 +1,11 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import jakarta.persistence.*; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; 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.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -12,37 +13,36 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; 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.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Date; 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 @Slf4j
@Controller @Controller
public class UrlRedirectionController { public class UrlRedirectionController {
@Autowired private final UrlShortenerRepository urlShortenerRepository;
private UrlShortenerRepository urlShortenerRepository;
@Autowired private final AnalyticsRepository analyticsRepository;
private AnalyticsRepository analyticsRepository;
@Autowired private final UrlShortenerService urlShortenerService;
private UrlShortenerService urlShortenerService;
@PersistenceContext @PersistenceContext
private EntityManager entityManager; private EntityManager entityManager;
@Autowired private final PasswordEncoder passwordEncoder;
private 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}") @PostMapping("{shortUrl}")
public String submitPassword(@PathVariable String 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.fasterxml.jackson.annotation.JsonIgnore;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType; import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageConfig; import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import jakarta.annotation.Nullable;
import jakarta.persistence.*; import jakarta.persistence.*;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -19,8 +17,8 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.*;
@Entity @Entity
@Table(name = "url_shortener") @Table(name = "url_shortener")
@ -203,8 +201,7 @@ public class UrlShortener {
return Base64.getEncoder().encodeToString(imageBytes); return Base64.getEncoder().encodeToString(imageBytes);
} catch (Exception e) { } catch (Exception e) {
// Handle exceptions appropriately // Handle exceptions appropriately
e.printStackTrace(); throw new UrlShortenerException("Error generating B64QR for url",e);
return null;
} }
} }
private void addLogoToQrCode(BufferedImage qrImage, String logoPath) throws IOException { private void addLogoToQrCode(BufferedImage qrImage, String logoPath) throws IOException {

View file

@ -1,13 +1,19 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication @SpringBootApplication
//@ComponentScan(basePackages = "com.bitmutex.shortener") //@ComponentScan(basePackages = "com.bitmutex.shortener")
public class UrlShortenerApplication { public class UrlShortenerApplication extends SpringBootServletInitializer {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(UrlShortenerApplication.class, 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 com.google.common.util.concurrent.RateLimiter;
import jakarta.servlet.http.HttpServletRequest; 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.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication; 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.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.bind.annotation.RequestBody;
import java.io.UnsupportedEncodingException; import java.util.*;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -36,26 +26,28 @@ public class UrlShortenerController {
@Value("${server.servlet.context-path:}") // Inject the context path from application properties @Value("${server.servlet.context-path:}") // Inject the context path from application properties
private String contextPath; private String contextPath;
@Autowired private final UrlShortenerService service;
private UrlShortenerService service;
@Autowired private final RateLimiter rateLimiter;
private RateLimiter rateLimiter;
@Autowired private final UrlShortenerRepository urlShortenerRepository;
private UrlShortenerRepository urlShortenerRepository;
@Autowired private final UserRepository userRepository;
private UserRepository userRepository;
@Value("${cooldown.duration}") @Value("${cooldown.duration}")
private long cooldownSeconds; private long cooldownSeconds;
@Autowired private final UserService userService;
private UserService userService;
private final ConcurrentHashMap<String, Long> cooldownMap = new ConcurrentHashMap<>(); 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") @PostMapping("/shorten")
@ -368,7 +360,7 @@ public class UrlShortenerController {
protected String getClientId() { protected String getClientId() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String clientIp = request.getRemoteAddr(); String clientIp = request.getRemoteAddr();
return "ip_" + clientIp; // Prefixing with "ip_" for clarity 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.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -30,17 +31,13 @@ import java.util.regex.Pattern;
@Slf4j @Slf4j
public class UrlShortenerService { public class UrlShortenerService {
@Autowired private final UrlShortenerRepository repository;
private UrlShortenerRepository repository;
@Autowired private final SubscriptionService subscriptionService;
private SubscriptionService subscriptionService;
@Autowired private final UserService userService;
private UserService userService;
@Autowired private final HttpServletRequest request; // Inject the HttpServletRequest
private HttpServletRequest request; // Inject the HttpServletRequest
@Value("${server.port}") // Inject the server port from application properties @Value("${server.port}") // Inject the server port from application properties
private String serverPort; private String serverPort;
@ -51,8 +48,15 @@ public class UrlShortenerService {
@Value("${server.base.url}") @Value("${server.base.url}")
private String serverBaseUrl; private String serverBaseUrl;
@Autowired private final PasswordEncoder passwordEncoder;
private 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) { public String shortenUrl(String jsonInput) {
try { try {
@ -79,9 +83,8 @@ public class UrlShortenerService {
String linkType = "short"; String linkType = "short";
// Get the user entity (you need to implement a method to fetch the current user entity) // 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 // Check if the user has exceeded the maximum short URL limit
int maxShortUrlLimit = Integer.parseInt(subscriptionService.getCurrentSubscriptionDetails(userEntity).get("maxShortUrl").toString()); 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 // 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 { try {
// Check if the URL has a scheme, if not, assume it is HTTP // Check if the URL has a scheme, if not, assume it is HTTP
if (!originalUrl.contains("://")) { if (!originalUrl.contains("://")) {
originalUrl = "http://" + originalUrl; originalUrl = "https://" + originalUrl;
} }
// Basic URL format check using regex // Basic URL format check using regex
@ -289,7 +292,7 @@ public class UrlShortenerService {
Long userId = getCurrentUserId(); Long userId = getCurrentUserId();
// Get the user entity (you need to implement a method to fetch the current user entity) // 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 // Check if the user has exceeded the bio pages limit
int currentBioPagesCount = repository.countBioPagesByUserId(userId); int currentBioPagesCount = repository.countBioPagesByUserId(userId);
@ -330,13 +333,9 @@ public class UrlShortenerService {
public List<UrlShortener> getUrlsByUserId(Long userId) { public List<UrlShortener> getUrlsByUserId(Long userId) {
try { try {
// Assume that UrlDetails is an entity representing the details of a URL // Assume that UrlDetails is an entity representing the details of a URL For simplicity, let's assume UrlDetails is the entity itself
List<UrlShortener> urlDetailsList = repository.findByUserId(userId); // We might need to convert entities to a DTO or customize the response based on your needs
return 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;
} catch (Exception e) { } catch (Exception e) {
// Handle exceptions or log them as needed // Handle exceptions or log them as needed
throw new UrlShortenerException("Error retrieving URLs by user ID", e); 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 { 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(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); 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(); 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) { } catch (IOException e) {
return false; throw new UrlShortenerException("Validation Error : Bad Response",e);
} }
} }
} }

View file

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

View file

@ -1,26 +1,21 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; 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 org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@Service @Service
public class UserDetailsServiceImpl implements UserDetailsService { public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired private final UserRepository userRepository;
private UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

View file

@ -3,8 +3,6 @@ package com.bitmutex.shortener;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Set;
@Entity @Entity
@Table(name = "user_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.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -18,19 +17,19 @@ import java.util.Optional;
@Service @Service
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
@Autowired private final SubscriptionService subscriptionService;
private SubscriptionService subscriptionService;
@Autowired private final EmailService emailService;
private EmailService emailService;
private final UserRepository userRepository; private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) { public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, SubscriptionService subscriptionService, EmailService emailService) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
this.subscriptionService = subscriptionService;
this.emailService = emailService;
} }

View file

@ -1,27 +1,29 @@
package com.bitmutex.shortener; package com.bitmutex.shortener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; 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; import java.util.Optional;
@Controller @Controller
public class VerificationController { public class VerificationController {
@Autowired private final UserService userService;
private UserService userService;
@Autowired private final UserRepository userRepository;
private UserRepository userRepository;
@Autowired private final OtpService otpService;
private OtpService otpService;
public VerificationController(UserService userService, UserRepository userRepository, OtpService otpService) {
this.userService = userService;
this.userRepository = userRepository;
this.otpService = otpService;
}
@GetMapping("/verify-registration") @GetMapping("/verify-registration")
@ -32,7 +34,7 @@ public class VerificationController {
@PostMapping("/verify-registration") @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(); //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 # DataSource settings
spring.datasource.url=jdbc:mysql://${database.ip}:${database.port}/${database.name} spring.datasource.url=jdbc:mysql://${database.ip}:${database.port}/${database.name}
spring.datasource.username=root spring.datasource.username=root
spring.datasource.password= spring.datasource.password=1234qwer
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@ -22,7 +22,7 @@ cooldown.duration=30
requests.per.second=1 requests.per.second=1
#Context Path(start with / and not end with /) #Context Path(start with / and not end with /)
server.servlet.context-path= server.servlet.context-path= /
#Server Port #Server Port
server.port=8080 server.port=8080
@ -37,7 +37,8 @@ spring.thymeleaf.cache=false
#logging level #logging level
logging.level.org.springframework.security: DEBUG logging.level.org.springframework.security= INFO
logging.level.root=INFO
#SCHEMAGEN #SCHEMAGEN
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create

View file

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

View file

@ -22,9 +22,36 @@
<!-- Company Logo with SVG --> <!-- Company Logo with SVG -->
<div class="flex items-center"> <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"> <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- 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" /> <!-- 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> </svg>
</div> </div>

View file

@ -37,9 +37,36 @@
<!-- Company Logo with SVG --> <!-- Company Logo with SVG -->
<div class="flex items-center"> <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"> <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- 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" /> <!-- 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> </svg>
</div> </div>

View file

@ -119,5 +119,4 @@
}); });
</script> </script>
</body> </body>
<div th:replace="layouts/footerLayout"></div>
</html> </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="mx-auto w-full max-w-screen-xl p-4 py-6 lg:py-8">
<div class="md:flex md:justify-between"> <div class="md:flex md:justify-between">
<div class="mb-6 md:mb-0"> <div class="mb-6 md:mb-0">
<a href="https://flowbite.com/" class="flex items-center"> <a href="/" 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> <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> </a>
</div> </div>
<div class="grid grid-cols-2 gap-8 sm:gap-6 sm:grid-cols-3"> <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> <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"> <ul class="text-gray-500 dark:text-gray-400 font-medium">
<li class="mb-4"> <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><br>
<li> <li>
<a href="https://tailwindcss.com/" class="hover:underline">Tailwind CSS</a> <a href="/docs-ui" class="hover:underline">JAR/WAR Artifact</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -23,7 +55,7 @@
<h2 class="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white">Follow us</h2> <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"> <ul class="text-gray-500 dark:text-gray-400 font-medium">
<li class="mb-4"> <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><br>
<li> <li>
<a href="https://discord.gg/4eeurUVvTy" class="hover:underline">Discord</a> <a href="https://discord.gg/4eeurUVvTy" class="hover:underline">Discord</a>
@ -45,7 +77,7 @@
</div> </div>
<hr class="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" /> <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"> <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> </span>
<div class="flex mt-4 sm:justify-center sm:mt-0"> <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"> <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 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"> <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"> <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 120 120">
<!-- 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" /> <!-- 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> </svg>
</div> </div>
<ul class="flex flex-wrap justify-start space-x-4 items-center w-full md:w-auto"> <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"> <div class="mb-8">
<ul class="flex border-b"> <ul class="flex border-b">
<li class="-mb-px mr-1"> <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>
<li class="-mb-px mr-1"> <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> </li>
</ul> </ul>
<div id="createBio" class="tab-content"> <div id="createBio" class="tab-content">
<div class="bg-white p-6 rounded-lg shadow-md mt-4"> <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 --> <!-- 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 --> <!-- 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> <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> </form>
@ -38,9 +40,15 @@
<div id="shortenURL" class="hidden tab-content"> <div id="shortenURL" class="hidden tab-content">
<div class="bg-white p-6 rounded-lg shadow-md mt-4"> <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 --> <!-- 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 --> <!-- 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> <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> </form>
@ -55,18 +63,28 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Feature Card 1 --> <!-- Feature Card 1 -->
<div class="bg-white p-6 rounded-lg shadow-md"> <div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 1</h3> <h3 class="text-xl font-bold mb-2"><i class="fas fa-link"></i> Custom Short URLs</h3>
<p class="text-gray-600">Description of feature 1.</p> <p class="text-gray-600">Generate custom short URLs that reflect your brand or content.</p>
</div> </div>
<!-- Feature Card 2 --> <!-- Feature Card 2 -->
<div class="bg-white p-6 rounded-lg shadow-md"> <div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 2</h3> <h3 class="text-xl font-bold mb-2"><i class="fas fa-lock"></i> Password Protection</h3>
<p class="text-gray-600">Description of feature 2.</p> <p class="text-gray-600">Secure your short URLs with password protection, ensuring access only to authorized users.</p>
</div> </div>
<!-- Feature Card 3 --> <!-- Feature Card 3 -->
<div class="bg-white p-6 rounded-lg shadow-md"> <div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-xl font-bold mb-2">Feature 3</h3> <h3 class="text-xl font-bold mb-2"><i class="fas fa-toggle-on"></i> Link State Management</h3>
<p class="text-gray-600">Description of feature 3.</p> <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> </div>
</div> </div>
@ -75,92 +93,109 @@
<div class="mb-8"> <div class="mb-8">
<h2 class="text-3xl font-bold mb-4 text-center">Pricing</h2> <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"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Pricing Card 1 --> <!-- Free Plan -->
<div class="bg-white p-6 rounded-lg shadow-md"> <div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<h3 class="text-xl font-bold mb-2">Basic Plan</h3> <h3 class="text-2xl font-bold mb-4">Free Plan</h3>
<p class="text-gray-600">$19.99/month</p> <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> </div>
<!-- Pricing Card 2 -->
<div class="bg-white p-6 rounded-lg shadow-md"> <!-- Basic Plan -->
<h3 class="text-xl font-bold mb-2">Pro Plan</h3> <div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<p class="text-gray-600">$39.99/month</p> <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> </div>
<!-- Pricing Card 3 -->
<div class="bg-white p-6 rounded-lg shadow-md"> <!-- Pro Plan -->
<h3 class="text-xl font-bold mb-2">Enterprise Plan</h3> <div class="border-solid border-2 border-sky-500 bg-white p-6 rounded-lg shadow-md text-center">
<p class="text-gray-600">Contact us for a quote</p> <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> </div>
</div> </div>
<!-- Flowbite Accordion Section -->
<!-- URL Shortener FAQ Section -->
<div class="bg-white rounded-lg overflow-hidden shadow-md"> <div class="bg-white rounded-lg overflow-hidden shadow-md">
<div id="accordion-open" data-accordion="open"> <div id="accordion-open" data-accordion="open">
<!-- FAQ Item 1 --> <!-- FAQ Item 1 -->
<h2 id="accordion-open-heading-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"> <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"> <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"> <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> <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> </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"> <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"/> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg> </svg>
</button> </button>
</h2> </h2>
<div id="accordion-open-body-1" class="hidden" aria-labelledby="accordion-open-heading-1"> <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"> <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="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 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> <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>
</div> <!-- FAQ Item 2 -->
<!-- FAQ Item 2 --> <h2 id="accordion-open-heading-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">
<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"> <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"> <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> <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> </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"> <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"/> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg> </svg>
</button> </button>
</h2> </h2>
<div id="accordion-open-body-2" class="hidden" aria-labelledby="accordion-open-heading-2"> <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"> <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="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">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> <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>
</div> <!-- FAQ Item 3 -->
<!-- FAQ Item 3 --> <h2 id="accordion-open-heading-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">
<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"> <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"> <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> <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> </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"> <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"/> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5"/>
</svg> </svg>
</button> </button>
</h2> </h2>
<div id="accordion-open-body-3" class="hidden" aria-labelledby="accordion-open-heading-3"> <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"> <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">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">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">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>
<p class="mb-2 text-gray-500 dark:text-gray-400">Learn more about these technologies:</p> </div>
<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>
</div> </div>
</div> </div>
</div>
</div> </div>
</div>
<!-- Call to Action Section --> <!-- Call to Action Section -->
<div class="bg-gray-800 text-white p-8 rounded-lg shadow-md text-center"> <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> <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 --> <!-- Add more sections for other Actuator endpoints as needed -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
// Fetch health data using JS fetch //Function to Fetch health data using JS fetch
fetch('/actuator/health') function fetchHealthData() {
.then(response => response.json()) fetch('/actuator/health')
.then(data => { .then(response => response.json())
// Update the content of the healthDataContainer with the fetched data .then(data => {
document.getElementById('healthDataContainer').innerText = JSON.stringify(data, null, 2); // Update the content of the healthDataContainer with the fetched data
document.getElementById('healthDataContainer').innerText = JSON.stringify(data, null, 2);
// Parse JSON data // Parse JSON data
var json = JSON.parse(JSON.stringify(data)); var json = JSON.parse(JSON.stringify(data));
// Update Component Status // Update Component Status
updateComponentStatus('app', json.components && json.components.custom); updateComponentStatus('app', json.components && json.components.custom);
updateComponentStatus('db', json.components && json.components.db); updateComponentStatus('db', json.components && json.components.db);
updateComponentStatus('diskSpace', json.components && json.components.diskSpace); updateComponentStatus('diskSpace', json.components && json.components.diskSpace);
updateComponentStatus('mail', json.components && json.components.mail); updateComponentStatus('mail', json.components && json.components.mail);
updateComponentStatus('ping', json.components && json.components.ping); updateComponentStatus('ping', json.components && json.components.ping);
// Extract disk space information // Extract disk space information
const diskSpace = json.components && json.components.diskSpace; const diskSpace = json.components && json.components.diskSpace;
// Prepare data for pie chart // Prepare data for pie chart
if (diskSpace && diskSpace.status === 'UP' && diskSpace.details) { if (diskSpace && diskSpace.status === 'UP' && diskSpace.details) {
const free = diskSpace.details.free; const free = diskSpace.details.free;
const total = diskSpace.details.total; const total = diskSpace.details.total;
console.log(free/100000); console.log(free / 100000);
console.log(total/100000); console.log(total / 100000);
// Create a disk space pie chart using Chart.js // Create a disk space pie chart using Chart.js
const ctx = document.getElementById('diskSpaceChart').getContext('2d'); const ctx = document.getElementById('diskSpaceChart').getContext('2d');
const diskSpaceChart = new Chart(ctx, { const diskSpaceChart = new Chart(ctx, {
type: 'pie', type: 'pie',
data: { data: {
labels: ['Total Space', 'Free Space'], labels: ['Total Space', 'Free Space'],
datasets: [{ datasets: [{
data: [total, free], data: [total, free],
backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)'], backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)'],
borderWidth: 2 borderWidth: 2
}] }]
} }
}); });
} else { } else {
console.error('Error: Disk space data not available or status is not UP.'); console.error('Error: Disk space data not available or status is not UP.');
} }
}) })
.catch(error => console.error('Error fetching health data:', error)); .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 to update component status
function updateComponentStatus(componentId, component) { function updateComponentStatus(componentId, component) {
@ -172,7 +180,7 @@
} }
</script> </script>
</div>
<div th:replace="layouts/footerLayout"></div> <div th:replace="layouts/footerLayout"></div>
</body> </body>
</html> </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>