-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from mehmetemredemir/main
feat: server code and setup script created
- Loading branch information
Showing
7 changed files
with
254 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"domain": "", | ||
"port": "", | ||
"account_available": 0, | ||
"firebase_configdst": "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
''' | ||
written to bypass CORS when testing on local device. Don't forget to run it!!! | ||
''' | ||
|
||
from http.server import SimpleHTTPRequestHandler, HTTPServer | ||
|
||
class CORSHTTPRequestHandler(SimpleHTTPRequestHandler): | ||
def end_headers(self): | ||
self.send_header('Access-Control-Allow-Origin', '*') | ||
super().end_headers() | ||
|
||
port = 8000 | ||
server_address = ('', port) | ||
httpd = HTTPServer(server_address, CORSHTTPRequestHandler) | ||
print(f'Running on port {port}...') | ||
httpd.serve_forever() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"type": "", | ||
"project_id": "", | ||
"private_key_id": "", | ||
"private_key": "", | ||
"client_email": "", | ||
"client_id": "", | ||
"auth_uri": "", | ||
"token_uri": "", | ||
"auth_provider_x509_cert_url": "", | ||
"client_x509_cert_url": "", | ||
"universe_domain": "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ name = "brevurl" | |
version = "0.1.0" | ||
description = "Open source, fast and modern URL shortener" | ||
authors = [ | ||
{name = "Mehmet Emre Demir"}, | ||
{ name = "Mehmet Emre Demir", email = "[email protected]"}, | ||
] | ||
maintainers = [ | ||
{name = "Çınar Civan", email = "[email protected]"}, | ||
|
@@ -19,6 +19,13 @@ keywords = ["url", "url-shortener", "brevurl", "link-shortener", "scihook", "ope | |
|
||
[tool.poetry.dependencies] | ||
python = "^3.10" | ||
flask = "*" | ||
firebase-admin = "*" | ||
flask-cors = "*" | ||
pyfiglet = "*" | ||
colorama = "*" | ||
|
||
# package versions will change | ||
|
||
[project.urls] | ||
Homepage = "https://opensource.scihook.org" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
from flask import Flask, request, jsonify, redirect | ||
from flask_cors import CORS | ||
import hashlib | ||
import firebase_admin | ||
from firebase_admin import credentials, firestore | ||
import json | ||
|
||
with open('brevurl_config.json file path', 'r') as file: | ||
brconfig = json.load(file) | ||
|
||
app = Flask(__name__) | ||
CORS(app) | ||
port = brconfig["port"] | ||
|
||
#Start firabase | ||
cred = credentials.Certificate(brconfig["firebase_configdst"]) | ||
firebase_admin.initialize_app(cred) | ||
db = firestore.client() | ||
|
||
|
||
def shorten_url(url): | ||
hashObject = hashlib.md5(url.encode()) | ||
shortHash = hashObject.hexdigest()[:6] | ||
return shortHash | ||
|
||
@app.route('/shorten', methods=['POST']) | ||
def shorten(): | ||
data = request.json | ||
originalUrl = data.get('url') | ||
customShort = data.get('short') | ||
|
||
if not originalUrl: | ||
return jsonify({'error': 'URL is required'}), 400 | ||
|
||
if customShort: | ||
doc_ref = db.collection('urls').document(customShort) | ||
if doc_ref.get().exists: | ||
return jsonify({'error': 'Custom short URL already exists'}), 400 | ||
short_url = customShort | ||
else: | ||
short_url = shorten_url(originalUrl) | ||
doc_ref = db.collection('urls').document(short_url) | ||
while doc_ref.get().exists: | ||
short_url = shorten_url(short_url + "0") | ||
doc_ref = db.collection('urls').document(short_url) | ||
|
||
|
||
db.collection('urls').document(short_url).set({ | ||
'original_url': originalUrl | ||
}) | ||
|
||
return jsonify({'short_url': f'{brconfig["domain"]}:{port}/{short_url}'}), 200 | ||
|
||
@app.route('/<short_url>', methods=['GET']) | ||
def redirect_to_url(short_url): | ||
doc_ref = db.collection('urls').document(short_url) | ||
doc = doc_ref.get() | ||
|
||
if not doc.exists: | ||
return jsonify({'error': 'URL not found'}), 404 | ||
|
||
original_url = doc.to_dict().get('original_url') | ||
return redirect(original_url) | ||
|
||
if __name__ == '__main__': | ||
app.run(debug=False,port=port) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import pyfiglet | ||
from colorama import Fore, Style, init | ||
import json | ||
import firebase_admin | ||
from firebase_admin import credentials, firestore | ||
import sys | ||
|
||
|
||
# Initialize colorama | ||
init() | ||
|
||
def print_ascii_art(text): | ||
ascii_art = pyfiglet.figlet_format(text) | ||
print(Fore.CYAN + ascii_art + Style.RESET_ALL) | ||
|
||
def read_config_file(file_path): | ||
with open(file_path, 'r') as file: | ||
return json.load(file) | ||
|
||
def write_config_file(file_path, data): | ||
with open(file_path, 'w') as file: | ||
json.dump(data, file, indent=4) | ||
|
||
def validate_firebase_config(config): | ||
for key, value in config.items(): | ||
if not value: | ||
print(Fore.RED + f"Empty value found for key: {key}. Please complete firebase config file." + Style.RESET_ALL) | ||
sys.exit() | ||
|
||
def get_user_input(prompt, valid_responses=None): | ||
while True: | ||
user_input = input(prompt).strip().lower() | ||
if valid_responses is None or user_input in valid_responses: | ||
return user_input | ||
print(Fore.RED + f"Invalid input. Please try again({valid_responses})" + Style.RESET_ALL) | ||
|
||
def main(): | ||
print_ascii_art("Brevurl") | ||
|
||
brconfig_dst = input("Please enter brevurl_config.json file destination: ") | ||
frconfig_dst = input("Please enter firebase_config.json file destination: ") | ||
|
||
try: | ||
firebase_config = read_config_file(frconfig_dst) | ||
brevurl_config = read_config_file(brconfig_dst) | ||
except (FileNotFoundError, json.JSONDecodeError) as e: | ||
print(Fore.RED + f"Error reading configuration files: {e}" + Style.RESET_ALL) | ||
sys.exit() | ||
|
||
print(Fore.YELLOW + "Checking firebase config file..." + Style.RESET_ALL) | ||
validate_firebase_config(firebase_config) | ||
print(Fore.GREEN + "Done" + Style.RESET_ALL) | ||
|
||
domain = input("Please enter the URL address where the server is located (e.g., https://example.com): ") | ||
port = input("Please enter the port that the server application will use (this port will be used for routing on nginx): ") | ||
account_available = get_user_input( | ||
"Would you like to activate the account system? (y/n): ", | ||
valid_responses={'y', 'n'} | ||
) | ||
account_bool = 1 if account_available == 'y' else 0 | ||
|
||
print(Fore.YELLOW + "Editing the brevurl config file..." + Style.RESET_ALL) | ||
|
||
|
||
|
||
|
||
brevurl_config.update({ | ||
"domain": domain, | ||
"account_available": account_bool, | ||
"port": port, | ||
"firebase_configdst": frconfig_dst | ||
}) | ||
|
||
try: | ||
# Initialize firebase | ||
cred = credentials.Certificate(frconfig_dst) | ||
firebase_admin.initialize_app(cred) | ||
db = firestore.client() | ||
db.collection("urls").document("scihook").set({'original_url': 'https://scihook.org/'}) #create url entry | ||
|
||
write_config_file(brconfig_dst, brevurl_config) | ||
print(Fore.GREEN + "Successfully adjusted! If you want to make changes in the future, you can use this script or edit the config JSON files directly." + Style.RESET_ALL) | ||
except Exception as e: | ||
print(Fore.RED + f"Error: {e}" + Style.RESET_ALL) | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>URL Shortener</title> | ||
<script> | ||
fetch('brevurl_config.json file path') | ||
//If you are testing on a local device, run test/cors.py and enter this as the path: http://localhost:8000/brevurl_config.json | ||
.then(response => { | ||
if (!response.ok) { | ||
throw new Error('Network response was not ok'); | ||
} | ||
return response.json(); | ||
}) | ||
.then(data => { | ||
window.sharedVariableport = data.port; | ||
window.sharedVariabledomain = data.domain; | ||
}) | ||
.catch(error => { | ||
console.error('Error:', error); | ||
}); | ||
</script> | ||
</head> | ||
<body> | ||
<h1>URL Shortener</h1> | ||
<form id="shorten-form"> | ||
<label for="url">URL:</label> | ||
<input type="text" id="url" name="url" required> | ||
<label for="short">Short Name(Optional):</label> | ||
<input type="text" id="short" name="short"> | ||
<button type="submit">Shorten it!</button> | ||
</form> | ||
<div id="result"></div> | ||
|
||
<script> | ||
document.getElementById('shorten-form').addEventListener('submit', async function(event) { | ||
event.preventDefault(); | ||
const url = document.getElementById('url').value; | ||
const short = document.getElementById('short').value || null; | ||
const response = await fetch(`${window.sharedVariabledomain}:${window.sharedVariableport}/shorten`, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ url: url, short: short }), | ||
}); | ||
const data = await response.json(); | ||
if (response.ok) { | ||
document.getElementById('result').innerHTML = `Short URL: <a href="${data.short_url}" target="_blank">${data.short_url}</a>`; | ||
} else { | ||
document.getElementById('result').textContent = `Error: ${data.error}`; | ||
} | ||
}); | ||
</script> | ||
</body> | ||
</html> |