Cloud Native Impl
This commit is contained in:
parent
ff528b7017
commit
16f4a85c40
58 changed files with 5834 additions and 321054 deletions
47
Dockerfile
Normal file
47
Dockerfile
Normal 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
3
META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: com.bitmutex.shortener.UrlShortenerApplication
|
||||||
|
|
815
create.sql
815
create.sql
File diff suppressed because one or more lines are too long
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
shortener-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "3306:3306"
|
306473
logs/shortener.log
306473
logs/shortener.log
File diff suppressed because it is too large
Load diff
BIN
out/artifacts/shortener_jar/shortener.jar
Normal file
BIN
out/artifacts/shortener_jar/shortener.jar
Normal file
Binary file not shown.
100
pom.xml
100
pom.xml
|
@ -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
161
server.xml
Normal 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 "%r" %s %b" />
|
||||||
|
|
||||||
|
</Host>
|
||||||
|
</Engine>
|
||||||
|
</Service>
|
||||||
|
</Server>
|
37
shortener.sh
Normal file
37
shortener.sh
Normal 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
|
18313
shortener_db_schema.sql
18313
shortener_db_schema.sql
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("/")
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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())) {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
3
src/main/resources/META-INF/MANIFEST.MF
Normal file
3
src/main/resources/META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: com.bitmutex.shortener.UrlShortenerApplication
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -119,5 +119,4 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
<div th:replace="layouts/footerLayout"></div>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,25 +93,43 @@
|
||||||
<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">
|
||||||
|
@ -103,7 +139,7 @@
|
||||||
<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"/>
|
||||||
|
@ -112,8 +148,8 @@
|
||||||
</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 -->
|
||||||
|
@ -122,7 +158,7 @@
|
||||||
<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"/>
|
||||||
|
@ -131,8 +167,8 @@
|
||||||
</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 -->
|
||||||
|
@ -141,7 +177,7 @@
|
||||||
<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"/>
|
||||||
|
@ -150,17 +186,16 @@
|
||||||
</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>
|
|
||||||
<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>
|
||||||
|
</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>
|
||||||
|
|
|
@ -102,7 +102,8 @@
|
||||||
<!-- 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
|
||||||
|
function fetchHealthData() {
|
||||||
fetch('/actuator/health')
|
fetch('/actuator/health')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
@ -148,7 +149,14 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.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>
|
||||||
|
|
9
src/main/webapp/WEB-INF/web.xml
Normal file
9
src/main/webapp/WEB-INF/web.xml
Normal 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>
|
Loading…
Reference in a new issue