├── CNAME ├── Procfile ├── instance └── app.db ├── templates ├── googledb6605d07a2ef7ce.html ├── robots.txt ├── B4A2B4B8494512CF82C2CF71D91C7285.txt ├── .well-known │ └── pki-validation │ │ └── B4A2B4B8494512CF82C2CF71D91C7285.txt ├── about.html ├── shorten.html ├── privacy_policy.html ├── contact.html ├── index.html ├── tou.html └── base.html ├── static ├── img │ ├── SQLite.webp │ ├── ShortLink.png │ ├── Flask_logo.webp │ ├── Greensock.webp │ ├── ShortLink.webp │ ├── Bootstrap_logo.webp │ ├── ShortLink Light.png │ ├── ShortLink Light.webp │ ├── Python-logo-notext.webp │ └── svg │ │ ├── Facebook.svg │ │ ├── LinkedIn.svg │ │ ├── Twitter.svg │ │ ├── Google.svg │ │ └── Instagram.svg ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── site.webmanifest ├── js │ └── main.js ├── css │ └── style.css ├── scss │ └── main.scss └── sitemap.xml ├── robots.txt ├── server.py ├── vercel.json ├── requirements.txt ├── LICENSE ├── About ShortLink.md ├── README.md └── main.py /CNAME: -------------------------------------------------------------------------------- 1 | etcd.tech 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn main:app 2 | -------------------------------------------------------------------------------- /instance/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/instance/app.db -------------------------------------------------------------------------------- /templates/googledb6605d07a2ef7ce.html: -------------------------------------------------------------------------------- 1 | google-site-verification: googledb6605d07a2ef7ce.html -------------------------------------------------------------------------------- /static/img/SQLite.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/SQLite.webp -------------------------------------------------------------------------------- /static/img/ShortLink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/ShortLink.png -------------------------------------------------------------------------------- /static/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/favicon.ico -------------------------------------------------------------------------------- /static/img/Flask_logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/Flask_logo.webp -------------------------------------------------------------------------------- /static/img/Greensock.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/Greensock.webp -------------------------------------------------------------------------------- /static/img/ShortLink.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/ShortLink.webp -------------------------------------------------------------------------------- /static/img/Bootstrap_logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/Bootstrap_logo.webp -------------------------------------------------------------------------------- /static/img/ShortLink Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/ShortLink Light.png -------------------------------------------------------------------------------- /static/img/ShortLink Light.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/ShortLink Light.webp -------------------------------------------------------------------------------- /static/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /static/img/Python-logo-notext.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/img/Python-logo-notext.webp -------------------------------------------------------------------------------- /static/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /templates/ 3 | Disallow: /instance/ 4 | Disallow: /static/ 5 | Sitemap: /static/sitemap.xml 6 | -------------------------------------------------------------------------------- /static/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haidar038/shortlink/HEAD/static/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /templates/ 3 | Disallow: /instance/ 4 | Disallow: /static/ 5 | Sitemap: /static/sitemap.xml 6 | -------------------------------------------------------------------------------- /templates/B4A2B4B8494512CF82C2CF71D91C7285.txt: -------------------------------------------------------------------------------- 1 | A7A6E445A44F858AC50918716000504A6ECA104A38EE41A11D95AA308E2481E4 2 | comodoca.com 3 | 8d813378cf0c296 4 | -------------------------------------------------------------------------------- /templates/.well-known/pki-validation/B4A2B4B8494512CF82C2CF71D91C7285.txt: -------------------------------------------------------------------------------- 1 | A7A6E445A44F858AC50918716000504A6ECA104A38EE41A11D95AA308E2481E4 2 | comodoca.com 3 | 8d813378cf0c296 4 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from gunicorn.app.wsgiapp import run 3 | if __name__ == '__main__': 4 | sys.argv = "gunicorn --bind 0.0.0.0:5151 app:app".split() 5 | sys.exit(run()) 6 | -------------------------------------------------------------------------------- /static/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "main.py", 6 | "use": "@vercel/python" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/main.py" 13 | } 14 | ], 15 | "env": { 16 | "FLASK_ENV": "production", 17 | "FLASK_APP": "main.py" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | email-validator==1.3.1 2 | Flask==2.3.1 3 | Flask-Limiter==3.3.0 4 | Flask-Mail==0.9.1 5 | Flask-Migrate==4.0.4 6 | Flask-SQLAlchemy==3.0.3 7 | Flask-Toastr==0.5.8 8 | Flask-WTF==1.1.1 9 | pysafeguard==1.0.2 10 | requests==2.28.2 11 | SQLAlchemy==2.0.7 12 | Werkzeug==2.3.4 13 | WTForms==3.0.1 14 | gunicorn>=20.1.0 15 | Flask-Compress==1.13 16 | pyScss==1.4.0 17 | jsmin==3.0.1 18 | cssmin==0.2.0 19 | Flask-Assets==2.0 20 | Flask-Caching==2.0.2 21 | -------------------------------------------------------------------------------- /static/img/svg/Facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | function copyToClipboard(){document.getElementById("shortened-url").select(),document.execCommand("copy");var o=document.getElementById("copy-toast");o.classList.add("show"),setTimeout(function(){o.classList.remove("show")},3e3)}function onScroll(o){lastScrollY=window.scrollY,scheduledAnimationFrame||(scheduledAnimationFrame=!0,requestAnimationFrame(readAndUpdatePage))}window.addEventListener("scroll",onScroll);const tooltipTriggerList=document.querySelectorAll('[data-bs-toggle="tooltip"]'),tooltipList=[...tooltipTriggerList].map(o=>new bootstrap.Tooltip(o)); 2 | -------------------------------------------------------------------------------- /static/img/svg/LinkedIn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | *,:after,:before{box-sizing:inherit;font-family:"IBM Plex Sans";font-display:swap}html{height:100%;box-sizing:border-box}body{background-color:#fdfdfd;color:#3a3a3a;min-height:100%;position:relative}h1,h2,h3,h4,h5,h6{font-weight:700}.input-group .btn{border-top-left-radius:0;border-bottom-left-radius:0}.toast{position:fixed;top:80px;right:20px;width:200px;z-index:1}.text-bg-blue{background-color:#4169e1!important;color:#fff!important}.card.text-bg-blue{border-color:#4169e1!important}.text-blue{color:#4169e1}.features svg{fill:#555!important;height:50px}.socials img{transition:.2s ease-in-out;filter:grayscale(1)}.socials img:hover{filter:grayscale(0)} 2 | -------------------------------------------------------------------------------- /static/scss/main.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: inherit; 5 | font-family: "IBM Plex Sans"; 6 | font-display: swap; 7 | } 8 | html { 9 | height: 100%; 10 | box-sizing: border-box; 11 | } 12 | body { 13 | background-color: #fdfdfd; 14 | color: #3a3a3a; 15 | min-height: 100%; 16 | position: relative; 17 | } 18 | h1, 19 | h2, 20 | h3, 21 | h4, 22 | h5, 23 | h6 { 24 | font-weight: 700; 25 | } 26 | .input-group { 27 | .btn { 28 | border-top-left-radius: 0; 29 | border-bottom-left-radius: 0; 30 | } 31 | } 32 | .toast { 33 | position: fixed; 34 | top: 80px; 35 | right: 20px; 36 | width: 200px; 37 | z-index: 1; 38 | } 39 | .text-bg-blue { 40 | background-color: #4169e1 !important; 41 | color: #ffffff !important; 42 | } 43 | .card { 44 | &.text-bg-blue { 45 | border-color: #4169e1 !important; 46 | } 47 | } 48 | .text-blue { 49 | color: #4169e1; 50 | } 51 | .features { 52 | svg { 53 | fill: #555 !important; 54 | height: 50px; 55 | } 56 | } 57 | .socials { 58 | img { 59 | transition: ease-in-out 0.2s; 60 | filter: grayscale(1); 61 | &:hover { 62 | filter: grayscale(0); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 M. Khaidar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/img/svg/Twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/img/svg/Google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | https://shortlink.up.railway.app/ 12 | 2023-04-30T06:26:15+00:00 13 | 1.00 14 | 15 | 16 | https://shortlink.up.railway.app/about 17 | 2023-04-30T06:26:15+00:00 18 | 0.80 19 | 20 | 21 | https://shortlink.up.railway.app/contact 22 | 2023-04-30T06:26:15+00:00 23 | 0.80 24 | 25 | 26 | https://shortlink.up.railway.app/privacy 27 | 2023-04-30T06:26:15+00:00 28 | 0.80 29 | 30 | 31 | https://shortlink.up.railway.app/tou 32 | 2023-04-30T06:26:15+00:00 33 | 0.80 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/img/svg/Instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /About ShortLink.md: -------------------------------------------------------------------------------- 1 | # URL Shortener Web Service 2 | My Name is M. Khaidar, and I am dedicated to providing you with the best solutions for shortening and managing your URLs. As a solo developer, I have utilized cutting-edge technology such as Python, Flask, SQLite, and Bootstrap to create a fast and reliable service that is both easy to use and visually appealing. 3 | 4 | ## Mission 5 | I pride myself on my commitment to customer satisfaction and strive to create a seamless user experience using the latest web development frameworks. My mission is to help businesses and individuals save time and increase productivity by simplifying the process of managing and sharing URLs. 6 | 7 | ## Features 8 | - Shorten long URLs with just one click 9 | - Keep track of clicks and user data for analytics 10 | - High speed and reliable service 11 | 12 | ## Security 13 | I understand the importance of reliable and secure URL management, and that's why I have taken all necessary measures to ensure the safety of your data. My service uses encryption and secure protocols to keep your information protected. 14 | 15 | ## Get Started 16 | Whether you need to shorten URLs for social media or track clicks for marketing campaigns, my platform provides a simple and effective solution that is tailored to your needs. Thank you for choosing my service, and I look forward to serving you! 17 | 18 | To get started, simply visit [ShortLink](https://shortlink.up.railway.app) and begin shortening and managing your URLs today! 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShortLink 2 | 3 | ![ShortLink](https://user-images.githubusercontent.com/62201903/232665775-c9aa403d-3aee-4fb9-9ed2-13f014197853.gif) 4 | 5 | ShortLink adalah sebuah website URL Shortener yang dibangun menggunakan Flask dan SQLite, dengan antarmuka pengguna yang dibangun menggunakan Bootstrap. Website ini dapat memendekkan URL panjang dan melacak jumlah klik atau pengunjung pada setiap tautan yang telah dibuat. 6 | 7 | ## Teknologi yang Digunakan 8 | - Python 3.11.2 9 | - Flask 2.2.3 10 | - Bootstrap 5.3.0 11 | - Bootstrap Icons 12 | - SQLite 13 | - ChatGPT 14 | 15 | ## Deployment 16 | Project ini dideploy ke [Railway](https://railway.app/) 17 | 18 | ## Fitur 19 | - Memendekkan URL panjang menjadi URL pendek 20 | - Melacak jumlah klik atau pengunjung untuk setiap tautan yang telah dibuat 21 | - Tampilan antarmuka pengguna yang responsif dan mudah digunakan untuk semua device 22 | 23 | ## Demo 24 | Anda dapat mengunjungi [ShortLink](https://shortlink.up.railway.app) untuk demonya. 25 | 26 | ## Instalasi 27 | Untuk menjalankan aplikasi ini, pertama-tama pastikan bahwa Python dan Flask telah terpasang di komputer Anda. Kemudian, ikuti langkah-langkah berikut: 28 | 29 | 1. Clone repositori ini ke dalam komputer Anda. 30 | 2. Buka terminal dan pindah ke direktori aplikasi. 31 | 3. Buat sebuah lingkungan virtual menggunakan perintah `python -m venv env`. 32 | 4. Aktifkan lingkungan virtual menggunakan perintah `env\Scripts\activate` pada Windows atau `source env/bin/activate` pada Linux atau MacOS. 33 | 5. Pasang dependensi yang dibutuhkan dengan menjalankan perintah `pip install -r requirements.txt`. 34 | 6. Jalankan aplikasi menggunakan perintah `python app.py`. 35 | 7. Buka browser dan akses URL `http://localhost:5000`. 36 | -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | About ShortLink 5 | {% endblock %} 6 | 7 | {% block container %} 8 |
9 |

About

10 |
11 | 12 |
13 |
14 |
15 | Bootstrap Logo 16 | Flask Logo 17 | Python Logo 18 | SQLite Logo 19 |
20 |
21 |
22 | 23 |

24 | My Name is 25 | M. Khaidar, I am dedicated to providing you with the best solutions for shortening and managing your URLs. As a solo developer, I have 35 | utilized cutting-edge technology such as Python, Flask, SQLite, and Bootstrap to create a fast and reliable service that is both 36 | easy to use and visually appealing. 37 |

38 |

39 | I pride myself on my commitment to customer satisfaction and strive to create a seamless user experience using the latest web 40 | development frameworks. My mission is to help businesses and individuals save time and increase productivity by simplifying the 41 | process of managing and sharing URLs. 42 |

43 |

44 | Whether you need to shorten URLs for social media or track clicks for marketing campaigns, my platform provides a simple and 45 | effective solution that is tailored to your needs. I understand the importance of reliable and secure URL management, and that's why 46 | I have taken all necessary measures to ensure the safety of your data. 47 |

48 |

Thank you for choosing my service, and I look forward to serving you!

49 |
50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /templates/shorten.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Your Shortened URL 5 | {% endblock %} 6 | 7 | {% block container %} 8 | 9 |
10 |
11 |
12 |

URL Shortened!

13 |
14 |
15 |

Here is you shortened URL:

16 |
17 | 18 | 22 |
23 | 28 |
29 |
30 | 31 |

32 | Original URL : 33 | {{ original_url }} 36 |

37 |

Total clicked : {{ clicked }}

38 | 39 |
40 | 49 |
50 | 51 | 52 | 55 |
56 |
57 |
58 | 59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /templates/privacy_policy.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Privacy Policy 5 | {% endblock %} 6 | 7 | {% block container %} 8 | 9 |
10 |

Privacy Policy

11 |
12 | 13 |

14 | This Privacy Policy describes how we collect, use, and share information about you when you use our website. By accessing or using 15 | our website, you agree to this Privacy Policy. 16 |

17 | Information We Collect 18 |

We may collect information about you when you use our website. This information may include:

19 | 30 | How We Use Information 31 |

We may use the information we collect about you to:

32 | 38 | Information Sharing 39 |

We may share information about you in the following circumstances:

40 | 49 | Data Security 50 |

51 | We take reasonable measures to protect your personal information from loss, theft, misuse, and unauthorized access, disclosure, 52 | alteration, and destruction. 53 |

54 | Changes to Privacy Policy 55 |

56 | We may update this Privacy Policy from time to time. We encourage you to review this Privacy Policy periodically to stay informed 57 | about our information practices. 58 |

59 | Contact Us 60 |

If you have any questions or concerns about our Privacy Policy, please contact us at haidar038@gmail.com

61 |
62 | 63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /templates/contact.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% block title %} Contact {% endblock %} {% block container %} 2 |
3 |

Contact

4 |
5 | 6 |

7 | Thank you for visiting my contact page. I do not provide a contact form on this page, but you can still reach me 8 | through my social media accounts. Please visit my social media accounts to provide feedback, suggestions, or 9 | questions related to the services I provide. I will be happy to hear from you and will respond to your message 10 | as soon as possible. Thank you for your visit and I hope to provide you with the best service possible. 11 |

12 | 13 |
14 |
15 |
You can contact me here:
16 |
17 |
18 | 26 | Facebook 27 | 28 |
29 |
30 | 38 | Google 39 | 40 |
41 |
42 | 50 | LinkedIn 51 | 52 |
53 |
54 | 62 | Twitter 63 | 64 |
65 |
66 | 74 | Instagram 75 | 76 |
77 |
78 |
79 |
80 |
81 | 82 | 88 | 89 | {% endblock %} 90 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, redirect, request, abort, flash, url_for, send_from_directory 2 | import string, random, secrets 3 | from flask_sqlalchemy import SQLAlchemy 4 | from datetime import datetime, timedelta 5 | from flask_migrate import Migrate 6 | from flask_limiter import Limiter 7 | from urllib.parse import urlparse 8 | from flask_mail import Mail, Message 9 | from flask_toastr import Toastr 10 | from flask_wtf import FlaskForm 11 | from wtforms import StringField, TextAreaField 12 | from wtforms.validators import DataRequired, url, Email 13 | from flask_compress import Compress 14 | from flask_assets import Environment, Bundle 15 | from flask_caching import Cache 16 | 17 | app = Flask(__name__) 18 | app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///app.db" 19 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 20 | app.config["RATELIMIT_DEFAULT"] = "100 per day" 21 | app.config["SECRET_KEY"] = secrets.token_hex(16) 22 | app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'logging_name': 'sqlalchemy'} 23 | app.config['MAIL_SERVER'] = 'smtp-relay.sendinblue.com' 24 | app.config['MAIL_PORT'] = 587 25 | app.config['MAIL_USE_SSL'] = True 26 | app.config['MAIL_USERNAME'] = 'etercode30@gmail.com' 27 | app.config['MAIL_PASSWORD'] = 'p8fdMwC39bac2POy' 28 | 29 | compress = Compress() 30 | toastr = Toastr(app) 31 | db = SQLAlchemy() 32 | mail = Mail(app) 33 | migrate = Migrate(app, db) 34 | db.init_app(app) 35 | compress.init_app(app) 36 | limiter = Limiter( 37 | app, 38 | default_limits=["100 per day"] 39 | ) 40 | 41 | # Models 42 | class ShortenedUrl(db.Model): 43 | id = db.Column(db.Integer, primary_key=True) 44 | original_url = db.Column(db.String(2048), nullable=False) 45 | short_url = db.Column(db.String(10), nullable=False, unique=True) 46 | created_at = db.Column(db.DateTime, default=datetime.utcnow) 47 | clicks = db.Column(db.Integer, default=0) 48 | 49 | def __repr__(self): 50 | return f"" 51 | 52 | # Create a form for URL input 53 | class UrlForm(FlaskForm): 54 | url = StringField('Long URL', validators=[DataRequired(), url()]) 55 | 56 | class ContactForm(FlaskForm): 57 | name = StringField('Name', validators=[DataRequired()]) 58 | email = StringField('Email', validators=[DataRequired(), Email()]) 59 | message = TextAreaField('Message', validators=[DataRequired()]) 60 | 61 | with app.app_context(): 62 | db.create_all() 63 | 64 | @app.route('/') 65 | def index(): 66 | form = UrlForm() 67 | urls = ShortenedUrl.query.all() 68 | return render_template('index.html', urls=urls, form=form, page="home") 69 | 70 | def sanitize_url(url): 71 | parsed_url = urlparse(url) 72 | if parsed_url.scheme not in ['http', 'https']: 73 | return None 74 | else: 75 | return parsed_url.geturl() 76 | 77 | @app.route('/', methods=['POST', 'GET']) 78 | @limiter.limit("10 per minute") 79 | def shorten(): 80 | form = UrlForm() 81 | if form.validate_on_submit(): 82 | original_url = form.url.data 83 | original_url = sanitize_url(original_url) 84 | if not original_url: 85 | # Handle invalid URL input 86 | flash('Invalid URL') 87 | return render_template('index.html') 88 | 89 | short_url = ''.join(random.choices( 90 | string.ascii_letters + string.digits, k=5)) 91 | 92 | # Check if the ShortenedUrl object already exists in the database 93 | shortened_url_object = ShortenedUrl.query.filter_by(original_url=original_url).first() 94 | if shortened_url_object is None: 95 | # The ShortenedUrl object does not exist in the database, so add it 96 | shortened_url_object = ShortenedUrl( 97 | original_url=original_url, short_url=short_url, created_at=datetime.utcnow()) 98 | db.session.add(shortened_url_object) 99 | db.session.commit() 100 | else: 101 | # The ShortenedUrl object already exists in the database, so do nothing 102 | pass 103 | 104 | # Render the shortened URL page with the new short URL 105 | return redirect(url_for('shorten_success', short_url=short_url)) 106 | 107 | return render_template('index.html', form=form) 108 | 109 | @app.route('/') 110 | def redirect_to_original_url(short_url): 111 | shortened_url = ShortenedUrl.query.filter_by(short_url=short_url).first() 112 | if shortened_url: 113 | # Increment the clicks count of the ShortenedUrl object 114 | shortened_url.clicks += 1 115 | db.session.commit() 116 | original_url = shortened_url.original_url 117 | # Redirect the user to the original URL 118 | return redirect(original_url) 119 | else: 120 | # If the shortened URL is not found, return a 404 error 121 | return abort(404) 122 | 123 | @app.route('/shorten-success/') 124 | def shorten_success(short_url): 125 | # Retrieve the original URL from the database 126 | shortened_url = ShortenedUrl.query.filter_by(short_url=short_url).first() 127 | clicked = shortened_url.clicks 128 | if shortened_url: 129 | original_url = shortened_url.original_url 130 | # Render the "shorten_success" page with the short URL and original URL 131 | return render_template('shorten.html', short_url=request.host_url + short_url, original_url=original_url, clicked=clicked, page="shorten") 132 | else: 133 | # If the shortened URL is not found, return a 404 error 134 | return abort(404) 135 | 136 | def delete_old_urls(): 137 | # Get all shortened URLs that were created more than 7 days ago 138 | old_urls = ShortenedUrl.query.filter(ShortenedUrl.created_at < datetime.utcnow() - timedelta(days=7)).all() 139 | 140 | # Delete the old shortened URLs 141 | for url in old_urls: 142 | db.session.delete(url) 143 | 144 | # Commit the changes to the database 145 | db.session.commit() 146 | 147 | @app.route('/about') 148 | def about(): 149 | return render_template('about.html', page="about") 150 | 151 | @app.route('/contact', methods=['POST', 'GET']) 152 | def contact(): 153 | return render_template('contact.html', page="contact") 154 | 155 | @app.route('/privacy') 156 | def privacy(): 157 | return render_template('privacy_policy.html', page="privacy") 158 | 159 | @app.route('/tou') 160 | def tou(): 161 | return render_template('tou.html', page="tou") 162 | 163 | if __name__ == "__main__": 164 | # app.run(debug=True, host='0.0.0.0', port=int(os.environ.get("PORT", 8080))) 165 | app.run(debug=True) 166 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | ShortLink: Shorten your URLs with us! 5 | {% endblock %} 6 | 7 | {% block container %} 8 | 9 |
10 |
11 |

Shorten Your URL Here!

12 |
13 |

Shorten your links, lengthen your reach with ShortLink

14 |
15 | {{ form.hidden_tag() }}{{ form.csrf_token }} 16 | 17 | 19 |
20 |
{{ form.url.label(class="mb-3") }} {{ form.url(class="form-control") }}
21 |
22 | 23 |
24 |
25 | {% if error %} 26 | 27 | {% endif %} 28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 |

Easy to use

50 |
51 |
52 | 53 | 56 | 57 |

The URL shortened as well

58 |
59 |
60 | 61 | 62 | 66 | 67 | 68 |

This is secure

69 |
70 |
71 | 72 | 73 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |

Responsive to all devices

99 |
100 |
101 |
102 | 103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /templates/tou.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Terms of Use 5 | {% endblock %} 6 | 7 | {% block container %} 8 | 9 |
10 |

Terms of Use

11 |
12 | 13 |

14 | These Terms of Use (the "Terms") govern your use of the website and services offered by ShortLink (the "Service"). Please read these 15 | Terms carefully before using the Service. By accessing or using the Service, you agree to be bound by these Terms. 16 |

17 |
    18 |
  1. 19 | Use of Service 20 |

    You may use the Service only for lawful purposes and in accordance with these Terms. You agree not to use the Service:

    21 |
      22 |
    • In any way that violates any applicable federal, state, local, or international law or regulation
    • 23 |
    • 24 | To transmit, or procure the sending of, any advertising or promotional material, including any "junk mail," "chain 25 | letter," "spam," or any other similar solicitation 26 |
    • 27 |
    • 28 | To impersonate or attempt to impersonate ShortLink or a ShortLink employee, another user, or any other person or entity 29 |
    • 30 |
    • 31 | To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of the Service, or which, as 32 | determined by ShortLink, may harm ShortLink or users of the Service or expose them to liability. 33 |
    • 34 |
    35 |
  2. 36 |
  3. 37 | User Content 38 |

    39 | The Service allows you to submit, upload, publish, or otherwise make available content, including but not limited to text, 40 | photographs, videos, and links (collectively, "User Content"). You retain ownership of any intellectual property rights that 41 | you hold in the User Content. By submitting User Content to the Service, you grant ShortLink a non-exclusive, transferable, 42 | sub-licensable, royalty-free, worldwide license to use, copy, modify, create derivative works based on, distribute, publicly 43 | display, publicly perform, and otherwise exploit in any manner such User Content in all formats and distribution channels 44 | now known or hereafter devised (including in connection with the Service and ShortLink's business and on third-party sites 45 | and services), without further notice to or consent from you, and without the requirement of payment to you or any other 46 | person or entity. 47 |

    48 |
  4. 49 |
  5. 50 | Proprietary Rights 51 |

    52 | The Service and its entire contents, features, and functionality (including but not limited to all information, software, 53 | text, displays, images, video, and audio, and the design, selection, and arrangement thereof), are owned by Shorten Your 54 | URL, its licensors, or other providers of such material and are protected by United States and international copyright, 55 | trademark, patent, trade secret, and other intellectual property or proprietary rights laws. 56 |

    57 |
  6. 58 |
  7. 59 | Disclaimer of Warranties 60 |

    61 | THE SERVICE IS PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR 62 | IMPLIED. ShortLink DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF 63 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. ShortLink DOES NOT REPRESENT OR WARRANT THAT 64 | THE SERVICE WILL BE UNINTERRUPTED, ERROR-FREE, SECURE, OR FREE FROM VIRUSES OR OTHER HARMFUL COMPONENTS. 65 |

    66 |
  8. 67 |
  9. 68 | Limitation of Liability 69 |

    70 | IN NO EVENT WILL ShortLink, ITS AFFILIATES, LICENSORS, SERVICE PROVIDERS, EMPLOYEES, AGENTS, OFFICERS, OR DIRECTORS BE 71 | LIABLE FOR DAMAGES OF ANY KIND, UNDER ANY LEGAL THEORY, ARISING OUT OF OR IN CONNECTION WITH YOUR USE, OR INABILITY TO USE, 72 | THE SERVICE, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING BUT NOT 73 | LIMITED TO, PERSONAL INJURY, PAIN AND SUFFERING, EMOTIONAL DISTRESS, LOSS OF REVENUE, LOSS OF PROFITS, LOSS OF BUSINESS OR 74 | ANTICIPATED SAVINGS, LOSS OF USE, LOSS OF GOODWILL, LOSS OF DATA, AND WHETHER CAUSED BY TORT (INCLUDING NEGLIGENCE), BREACH 75 | OF CONTRACT, OR OTHERWISE, EVEN IF FORESEEABLE. 76 |

    77 |
  10. 78 |
  11. 79 | Indemnification 80 |

    81 | You agree to defend, indemnify, and hold harmless ShortLink, its affiliates, licensors, and service providers, and its and 82 | their respective officers, directors, employees, contractors, agents, licensors, suppliers, successors, and assigns from and 83 | against any claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees (including reasonable 84 | attorneys' fees) arising out of or relating to your violation of these Terms or your use of the Service, including, but not 85 | limited to, your User Content, any use of the Service's content, services, and products other than as expressly authorized 86 | in these Terms, or your use of any information obtained from the Service. 87 |

    88 |
  12. 89 |
  13. 90 | Modification of Terms 91 |

    92 | ShortLink reserves the right, in its sole discretion, to modify or replace any of these Terms at any time. It is your 93 | responsibility to check these Terms periodically for changes. Your continued use of the Service following the posting of any 94 | changes to these Terms constitutes acceptance of those changes. 95 |

    96 |
  14. 97 |
  15. 98 | Termination 99 |

    100 | ShortLink may terminate your access to all or any part of the Service at any time, with or without cause, with or without 101 | notice, effective immediately. If you wish to terminate these Term, you may simply discontinue using the Service. 102 |

    103 |
  16. 104 |
  17. 105 | Governing Law 106 |

    107 | These Terms and your use of the Service shall be governed by and construed in accordance with the laws of the Indonesia, 108 | without giving effect to any principles of conflicts of law. 109 |

    110 |
  18. 111 |
  19. 112 | Entire Agreement 113 |

    114 | These Terms constitute the entire agreement between you and ShortLink with respect to the use of the Service and supersede 115 | all prior or contemporaneous communications and proposals, whether oral or written, between you and Shorten Your URL with 116 | respect to the Service. 117 |

    118 |
  20. 119 |
  21. 120 | Contact Information 121 |

    122 | If you have any questions about these Terms, please contact ShortLink at 123 | this page. 124 |

    125 |
  22. 126 |
127 |
128 | 129 | {% endblock %} 130 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% block title %} {% endblock %} 19 | 20 | 21 | 27 | 34 | 41 | 42 | 43 | 44 | 51 | 57 | 58 | 62 | 63 | 64 | 65 | 71 | 76 | 77 | 78 | {{ toastr.include_toastr_css() }} {{ toastr.message() }} 79 | 80 | 81 | 82 | 83 | 84 | 93 | 94 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 | 153 |
154 | 155 |
{% block container %} {% endblock %}
156 |
157 | 158 |
159 |
160 |
161 |

© 2023 ShortLink. All rights reserved.

162 |
163 | 175 |
176 |
177 |
178 | 179 | 180 | {{ toastr.include_toastr_js() }} 181 | 186 | 191 | 192 | 193 | 194 | --------------------------------------------------------------------------------