Skip to content

Commit d1e945b

Browse files
committed
base code added
Base Code for URL Shortener
1 parent 888f5db commit d1e945b

File tree

9 files changed

+334
-0
lines changed

9 files changed

+334
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.pyc
2+
/__pycache__
3+
.env
4+
client_secret.json
5+
do_not_commit
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# mbsa.in
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from flask import Flask, request, render_template, redirect, flash
2+
from helpers import random_string_generator, random_string_generator_only_alpha, is_valid_url
3+
from db import url_data_collection
4+
import os
5+
from datetime import datetime
6+
7+
8+
app = Flask(__name__)
9+
app.secret_key = os.environ['APP_SECRET']
10+
11+
url_ = os.environ['APP_URL']
12+
13+
reserved_keywords = ['login', 'logout', 'shorten-url']
14+
15+
16+
@app.route("/", methods = ['GET'])
17+
def start():
18+
return render_template("index.html")
19+
20+
@app.route("/shorten-url", methods=['POST'])
21+
def shorten_url():
22+
data = request.form
23+
url_input = data.get("url_input")
24+
data_id = random_string_generator_only_alpha(10)
25+
if not is_valid_url(str(url_input)):
26+
flash({'type':'error', 'data':"Inavlid URL"})
27+
return redirect("/")
28+
random_slug = random_string_generator(7)
29+
shortened_url = f"{url_}/{random_slug}"
30+
shortened_url_ = shortened_url[len(url_)+1:]
31+
if shortened_url == url_input:
32+
flash({'type':'error', 'data':"Infinite Redirect Error!"})
33+
return redirect("/")
34+
first_keyword = shortened_url_.split("/")[0]
35+
if first_keyword in reserved_keywords:
36+
flash({'type':'error', 'data':f"{first_keyword} is a reserved keyword! Please use any other word!"})
37+
return redirect("/")
38+
incoming_data = {
39+
"no_of_/": shortened_url_.count("/"),
40+
"order": shortened_url_.split("/"),
41+
"redirect_url": url_input,
42+
"shortened_url": shortened_url,
43+
"data_id": data_id,
44+
"created_at": datetime.now()
45+
}
46+
if url_details := url_data_collection.find_one({"no_of_/": incoming_data['no_of_/'], "order":incoming_data['order']}):
47+
flash({'type':'error', 'data':"This Domain is Already Taken!"})
48+
return redirect("/")
49+
url_data_collection.insert_one(incoming_data)
50+
flash({'type':'data', 'data':shortened_url})
51+
return redirect("/")
52+
53+
54+
@app.before_request
55+
def before_request_func():
56+
host_url = request.host_url
57+
url = request.url
58+
url = url[len(host_url):]
59+
incoming_data = {
60+
"no_of_/":url.count("/"),
61+
"order":url.split("/")
62+
}
63+
if url_details := url_data_collection.find_one({"no_of_/": incoming_data['no_of_/'], "order":incoming_data['order']}):
64+
redirect_url = url_details.get("redirect_url")
65+
return redirect(redirect_url)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
import pymongo
3+
4+
ENVIRONMENT = os.environ["ENVIRONMENT"]
5+
if ENVIRONMENT == "local":
6+
connection_string = "mongodb://localhost:27017"
7+
DB_NAME = "url_shortener"
8+
else:
9+
MONGO_CLUSTER = os.environ["MONGO_URI"]
10+
MONGO_USERNAME = os.environ["MONGO_USERNAME"]
11+
MONGO_PASSWORD = os.environ["MONGO_PASSWORD"]
12+
DB_NAME = os.environ["DB_NAME"]
13+
connection_string = f"mongodb+srv://{MONGO_USERNAME}:{MONGO_PASSWORD}@{MONGO_CLUSTER}/?retryWrites=true&ssl=true&ssl_cert_reqs=CERT_NONE&w=majority"
14+
15+
16+
db_client = pymongo.MongoClient(connection_string)
17+
db_client = db_client.get_database(DB_NAME)
18+
19+
url_data_collection = db_client['url_data']
20+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import string
2+
import random
3+
import re
4+
5+
def random_string_generator(N):
6+
res = ''.join(random.choices(string.ascii_lowercase +
7+
string.digits, k=N))
8+
return str(res)
9+
10+
def random_string_generator_only_alpha(N):
11+
res = ''.join(random.choices(string.ascii_lowercase +
12+
string.ascii_uppercase, k=N))
13+
return str(res)
14+
15+
16+
def is_valid_url(url):
17+
# Define a regular expression pattern to match URLs
18+
url_pattern = re.compile(
19+
r'^(https?|ftp)://' # Match the scheme (http, https, or ftp)
20+
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # Match domain
21+
r'localhost|' # Match "localhost"
22+
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # Match IP address
23+
r'(?::\d+)?' # Match optional port number
24+
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
25+
26+
return bool(url_pattern.match(url))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flask==2.0.1
2+
pymongo[srv]==4.3.3
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from app import app
2+
if __name__ == '__main__':
3+
app.run(debug=True)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source .env
2+
python run.py
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
2+
<!doctype html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<meta name="description" content="">
8+
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
9+
<meta name="generator" content="Hugo 0.104.2">
10+
<title>URL Shortener</title>
11+
<link rel="canonical" href="https://getbootstrap.com/docs/5.2/examples/checkout/">
12+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
13+
<meta name="theme-color" content="#712cf9">
14+
15+
16+
<style>
17+
div.container{
18+
overflow-x: hidden;
19+
}
20+
.bd-placeholder-img {
21+
font-size: 1.125rem;
22+
text-anchor: middle;
23+
-webkit-user-select: none;
24+
-moz-user-select: none;
25+
user-select: none;
26+
}
27+
28+
@media (min-width: 768px) {
29+
.bd-placeholder-img-lg {
30+
font-size: 3.5rem;
31+
}
32+
}
33+
34+
.b-example-divider {
35+
height: 3rem;
36+
background-color: rgba(0, 0, 0, .1);
37+
border: solid rgba(0, 0, 0, .15);
38+
border-width: 1px 0;
39+
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
40+
}
41+
42+
.b-example-vr {
43+
flex-shrink: 0;
44+
width: 1.5rem;
45+
height: 100vh;
46+
}
47+
48+
.bi {
49+
vertical-align: -.125em;
50+
fill: currentColor;
51+
}
52+
53+
.nav-scroller {
54+
position: relative;
55+
z-index: 2;
56+
height: 2.75rem;
57+
overflow-y: hidden;
58+
}
59+
60+
.nav-scroller .nav {
61+
display: flex;
62+
flex-wrap: nowrap;
63+
padding-bottom: 1rem;
64+
margin-top: -1px;
65+
overflow-x: auto;
66+
text-align: center;
67+
white-space: nowrap;
68+
-webkit-overflow-scrolling: touch;
69+
}
70+
.container {
71+
max-width: 960px;
72+
}
73+
74+
75+
</style>
76+
77+
78+
<!-- Custom styles for this template -->
79+
<link href="form-validation.css" rel="stylesheet">
80+
</head>
81+
82+
<body class="bg-light">
83+
84+
<div class="container">
85+
86+
87+
88+
{% with messages = get_flashed_messages() %}
89+
{% if messages %}
90+
{% for message in messages %}
91+
{% if message['type'] == 'data' %}
92+
<div class="card bg-success" style="width: 100%;">
93+
<ul class="list-group list-group-flush">
94+
<li class="list-group-item text-center"><b id="message">{{message['data']}}</b><br><button class="btn btn-sm btn-primary my-1" onclick="copyToClipboard('message')">Copy To Clipboard</button>
95+
</li>
96+
</ul>
97+
</div>
98+
{% elif message['type'] == 'error' %}
99+
<div class="card bg-danger" style="width: 100%;">
100+
<ul class="list-group list-group-flush">
101+
<li class="list-group-item text-center">{{message['data']}}</li>
102+
</ul>
103+
</div>
104+
{% endif %}
105+
{% endfor %}
106+
{% endif %}
107+
{% endwith %}
108+
<main>
109+
<div class="py-5 text-center">
110+
111+
<h2><b>URL Shortening using Flask</b></h2>
112+
</div>
113+
114+
<div class="row g-5">
115+
116+
<div class="col-md-12 col-lg-12">
117+
<h4 class="mb-3">Paste the URL to be Shortened</h4>
118+
<form class="needs-validation" action="/shorten-url" method="post" id="urlForm" onsubmit="return Validate()">
119+
<div class="row g-3">
120+
<div class="col-sm-12">
121+
<label for="firstName" class="form-label">URL</label>
122+
<input type="text" class="form-control" id="url" name="url_input" placeholder="Paste the Long URL" required>
123+
</div>
124+
</div>
125+
<hr class="my-4">
126+
<button class="w-100 btn btn-primary btn-lg" type="submit">Shorten</button>
127+
</form>
128+
</div>
129+
</div>
130+
</main>
131+
132+
<footer class="my-5 pt-5 text-muted text-center text-small">
133+
<p class="mb-1" style="font-size: 14px;">Do give your valuable feedback at <a href="mailto:[email protected]">[email protected]</a>!</p>
134+
<p class="mb-1">Made with ♥ by <a href="https://github.com/MBSA-INFINITY" target="_blank">MBSA</a></p>
135+
</footer>
136+
</div>
137+
138+
139+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
140+
141+
<script>
142+
// Example starter JavaScript for disabling form submissions if there are invalid fields
143+
(() => {
144+
'use strict'
145+
146+
// Fetch all the forms we want to apply custom Bootstrap validation styles to
147+
const forms = document.querySelectorAll('.needs-validation')
148+
149+
// Loop over them and prevent submission
150+
Array.from(forms).forEach(form => {
151+
form.addEventListener('submit', event => {
152+
if (!form.checkValidity()) {
153+
event.preventDefault()
154+
event.stopPropagation()
155+
}
156+
157+
form.classList.add('was-validated')
158+
}, false)
159+
})
160+
})()
161+
</script>
162+
163+
<script>
164+
function Validate(){
165+
const urlInput = document.getElementById('urlInput').value;
166+
const validationResult = document.getElementById('validationResult');
167+
if (isValidURL(urlInput)) {
168+
return true
169+
} else {
170+
alert("Invalid URL")
171+
return false
172+
}
173+
}
174+
175+
function isValidURL(url) {
176+
const pattern = new RegExp('^(https?:\\/\\/)?'+ // Protocol
177+
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // Domain name
178+
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR IPv4
179+
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // Port and path
180+
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // Query string
181+
'(\\#[-a-z\\d_]*)?$','i'); // Fragment locator
182+
return pattern.test(url);
183+
}
184+
function copyToClipboard(id) {
185+
const text = document.getElementById(id).textContent;
186+
187+
// Create a temporary input element to copy the text
188+
const input = document.createElement('input');
189+
input.value = text;
190+
191+
// Append the input element to the document
192+
document.body.appendChild(input);
193+
194+
// Select the text inside the input element
195+
input.select();
196+
197+
// Copy the selected text to the clipboard
198+
document.execCommand('copy');
199+
200+
// Remove the temporary input element
201+
document.body.removeChild(input);
202+
203+
// Alert the copied text
204+
alert("Copied the Clipboard");
205+
}
206+
207+
</script>
208+
209+
</body>
210+
</html>

0 commit comments

Comments
 (0)