Microservice for redirect handling
This commit is contained in:
parent
ff341ddcb5
commit
9d5ca8b123
@ -1,49 +0,0 @@
|
|||||||
from flask import redirect, jsonify, request, Response
|
|
||||||
|
|
||||||
from api.route import Route
|
|
||||||
from core.logger import Logger
|
|
||||||
from data.schemas.public.short_url import ShortUrl
|
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
|
||||||
from data.schemas.public.short_url_visit import ShortUrlVisit
|
|
||||||
from data.schemas.public.short_url_visit_dao import shortUrlVisitDao
|
|
||||||
|
|
||||||
logger = Logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Route.get(f"/api/find/<path:path>")
|
|
||||||
async def find(path: str):
|
|
||||||
from_db = await shortUrlDao.find_single_by({ShortUrl.short_url: path})
|
|
||||||
if from_db is None:
|
|
||||||
return jsonify(None)
|
|
||||||
|
|
||||||
return jsonify(from_db.to_dto())
|
|
||||||
|
|
||||||
|
|
||||||
@Route.get(f"/api/redirect/<path:path>")
|
|
||||||
async def handle_short_url(path: str):
|
|
||||||
if path.startswith("api/"):
|
|
||||||
path = path.replace("api/", "")
|
|
||||||
|
|
||||||
from_db = await shortUrlDao.find_single_by({ShortUrl.short_url: path})
|
|
||||||
if from_db is None:
|
|
||||||
return {"error": "Short URL not found"}, 404
|
|
||||||
|
|
||||||
try:
|
|
||||||
await shortUrlVisitDao.create(
|
|
||||||
ShortUrlVisit(
|
|
||||||
0,
|
|
||||||
from_db.id,
|
|
||||||
request.headers.get("User-Agent")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to update short url {from_db.short_url} with error", e)
|
|
||||||
|
|
||||||
return _do_redirect(from_db.target_url)
|
|
||||||
|
|
||||||
def _do_redirect(url: str) -> Response:
|
|
||||||
# todo: multiple protocols like ts3://
|
|
||||||
if not url.startswith("http://") and not url.startswith("https://"):
|
|
||||||
url = f"http://{url}"
|
|
||||||
|
|
||||||
return redirect(url)
|
|
@ -1,4 +1,9 @@
|
|||||||
|
from core.database.database_settings import DatabaseSettings
|
||||||
from core.database.db_context import DBContext
|
from core.database.db_context import DBContext
|
||||||
|
from core.environment import Environment
|
||||||
|
from core.logger import DBLogger
|
||||||
|
|
||||||
|
logger = DBLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
@ -14,3 +19,30 @@ class Database:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def connect(cls):
|
def connect(cls):
|
||||||
cls._db.connect()
|
cls._db.connect()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def startup_db(cls):
|
||||||
|
from data.service.migration_service import MigrationService
|
||||||
|
|
||||||
|
logger.info("Init DB")
|
||||||
|
db = DBContext()
|
||||||
|
host = Environment.get("DB_HOST", str)
|
||||||
|
port = Environment.get("DB_PORT", int)
|
||||||
|
user = Environment.get("DB_USER", str)
|
||||||
|
password = Environment.get("DB_PASSWORD", str)
|
||||||
|
database = Environment.get("DB_DATABASE", str)
|
||||||
|
|
||||||
|
if None in [host, port, user, password, database]:
|
||||||
|
logger.fatal(
|
||||||
|
"DB settings are not set correctly",
|
||||||
|
EnvironmentError("DB settings are not set correctly"),
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.connect(
|
||||||
|
DatabaseSettings(
|
||||||
|
host=host, port=port, user=user, password=password, database=database
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Database.init(db)
|
||||||
|
migrations = MigrationService(db)
|
||||||
|
await migrations.migrate()
|
||||||
|
117
api/src/redirector.py
Normal file
117
api/src/redirector.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
from eventlet import wsgi
|
||||||
|
from flask import Flask, request, Response, redirect, render_template
|
||||||
|
|
||||||
|
from core.database.database import Database
|
||||||
|
from core.environment import Environment
|
||||||
|
from core.logger import Logger
|
||||||
|
from data.schemas.public.short_url import ShortUrl
|
||||||
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
|
from data.schemas.public.short_url_visit import ShortUrlVisit
|
||||||
|
from data.schemas.public.short_url_visit_dao import shortUrlVisitDao
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Redirector(Flask):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Flask.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
app = Redirector(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/<path:path>")
|
||||||
|
async def _handle_request(path: str):
|
||||||
|
short_url = await _find_short_url_by_url(path)
|
||||||
|
if short_url is None:
|
||||||
|
return render_template("404.html"), 404
|
||||||
|
|
||||||
|
user_agent = request.headers.get("User-Agent", "").lower()
|
||||||
|
|
||||||
|
if "wheregoes" in user_agent or "someothertool" in user_agent:
|
||||||
|
return await _handle_short_url(path, short_url)
|
||||||
|
|
||||||
|
if short_url.loading_screen:
|
||||||
|
return render_template(
|
||||||
|
"redirect.html",
|
||||||
|
key=short_url.short_url,
|
||||||
|
target_url=_get_redirect_url(short_url.target_url),
|
||||||
|
)
|
||||||
|
|
||||||
|
return await _handle_short_url(path, short_url)
|
||||||
|
|
||||||
|
|
||||||
|
async def _handle_short_url(path: str, short_url: ShortUrl):
|
||||||
|
if path.startswith("api/"):
|
||||||
|
path = path.replace("api/", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await shortUrlVisitDao.create(
|
||||||
|
ShortUrlVisit(0, short_url.id, request.headers.get("User-Agent"))
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to update short url {short_url.short_url} with error", e)
|
||||||
|
|
||||||
|
return _do_redirect(short_url.target_url)
|
||||||
|
|
||||||
|
|
||||||
|
async def _find_short_url_by_url(url: str) -> ShortUrl:
|
||||||
|
return await shortUrlDao.find_single_by({ShortUrl.short_url: url})
|
||||||
|
|
||||||
|
|
||||||
|
def _get_redirect_url(url: str) -> str:
|
||||||
|
# todo: multiple protocols like ts3://
|
||||||
|
if not url.startswith("http://") and not url.startswith("https://"):
|
||||||
|
url = f"http://{url}"
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def _do_redirect(url: str) -> Response:
|
||||||
|
return redirect(_get_redirect_url(url))
|
||||||
|
|
||||||
|
|
||||||
|
async def configure():
|
||||||
|
Logger.set_level(Environment.get("LOG_LEVEL", str, "info"))
|
||||||
|
Environment.set_environment(Environment.get("ENVIRONMENT", str, "production"))
|
||||||
|
logger.info(f"Environment: {Environment.get_environment()}")
|
||||||
|
|
||||||
|
app.debug = Environment.get_environment() == "development"
|
||||||
|
|
||||||
|
await Database.startup_db()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
from asyncio import WindowsSelectorEventLoopPolicy
|
||||||
|
|
||||||
|
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
||||||
|
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
loop.run_until_complete(configure())
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
port = Environment.get("PORT", int, 5001)
|
||||||
|
logger.info(f"Start API on port: {port}")
|
||||||
|
if Environment.get_environment() == "development":
|
||||||
|
logger.info(f"Playground: http://localhost:{port}/")
|
||||||
|
wsgi.server(eventlet.listen(("0.0.0.0", port)), app, log_output=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
# ((
|
||||||
|
# ( )
|
||||||
|
# ; / ,
|
||||||
|
# / \/
|
||||||
|
# / |
|
||||||
|
# / ~/
|
||||||
|
# / ) ) ~ edraft
|
||||||
|
# ___// | /
|
||||||
|
# --' \_~-,
|
@ -19,30 +19,6 @@ logger = Logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Startup:
|
class Startup:
|
||||||
@staticmethod
|
|
||||||
async def _startup_db():
|
|
||||||
logger.info("Init DB")
|
|
||||||
db = DBContext()
|
|
||||||
host = Environment.get("DB_HOST", str)
|
|
||||||
port = Environment.get("DB_PORT", int)
|
|
||||||
user = Environment.get("DB_USER", str)
|
|
||||||
password = Environment.get("DB_PASSWORD", str)
|
|
||||||
database = Environment.get("DB_DATABASE", str)
|
|
||||||
|
|
||||||
if None in [host, port, user, password, database]:
|
|
||||||
logger.fatal(
|
|
||||||
"DB settings are not set correctly",
|
|
||||||
EnvironmentError("DB settings are not set correctly"),
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.connect(
|
|
||||||
DatabaseSettings(
|
|
||||||
host=host, port=port, user=user, password=password, database=database
|
|
||||||
)
|
|
||||||
)
|
|
||||||
Database.init(db)
|
|
||||||
migrations = MigrationService(db)
|
|
||||||
await migrations.migrate()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _seed_data():
|
async def _seed_data():
|
||||||
@ -69,7 +45,7 @@ class Startup:
|
|||||||
|
|
||||||
app.debug = Environment.get_environment() == "development"
|
app.debug = Environment.get_environment() == "development"
|
||||||
|
|
||||||
await cls._startup_db()
|
await Database.startup_db()
|
||||||
await FileService.clean_files()
|
await FileService.clean_files()
|
||||||
|
|
||||||
await cls._seed_data()
|
await cls._seed_data()
|
||||||
|
BIN
api/src/static/custom/background.jpg
Normal file
BIN
api/src/static/custom/background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 354 KiB |
BIN
api/src/static/custom/logo.png
Normal file
BIN
api/src/static/custom/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
BIN
api/src/static/not_found.gif
Normal file
BIN
api/src/static/not_found.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 MiB |
27
api/src/static/styles.css
Normal file
27
api/src/static/styles.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
body {
|
||||||
|
background-color: #1e293b;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #a2271f;
|
||||||
|
font-size: 3rem !important;
|
||||||
|
font-weight: 800 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-2 {
|
||||||
|
background-color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 40px;
|
||||||
|
|
||||||
|
padding: 35px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
18
api/src/templates/404.html
Normal file
18
api/src/templates/404.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>404 - Not found</title>
|
||||||
|
<link rel="stylesheet" href="/static/styles.css">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="w-full h-full flex flex-col justify-center items-center">
|
||||||
|
<div class="bg-2 tile">
|
||||||
|
<h1 class="flex justify-center items-center">404 - Not found</h1>
|
||||||
|
<img src="/static/not_found.gif" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
api/src/templates/redirect.html
Normal file
39
api/src/templates/redirect.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Redirecting {{ key }}</title>
|
||||||
|
<link rel="stylesheet" href="/static/styles.css">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="w-full h-full flex flex-col justify-center items-center">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="relative w-screen h-screen bg-cover bg-center"
|
||||||
|
style="background-image: url('/static/custom/background.jpg')"></div>
|
||||||
|
|
||||||
|
<div class="absolute w-1/3 h-2/5 rounded-xl p-5 flex flex-col gap-5 justify-center items-center">
|
||||||
|
<div class="absolute inset-0 bg-black opacity-70 rounded-xl"></div>
|
||||||
|
<div class="relative logo flex justify-center items-center">
|
||||||
|
<img class="h-48" src="/static/custom/logo.png" alt="logo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="relative text-3xl text-white">Redirecting...</h1>
|
||||||
|
<p class="relative text-white">You will be redirected in <span id="countdown">5</span> seconds.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let countdown = 5;
|
||||||
|
const countdownElement = document.getElementById('countdown');
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
countdownElement.textContent = countdown;
|
||||||
|
if (countdown <= 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
window.location.href = '{{ target_url }}';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1 +1 @@
|
|||||||
0.2.1
|
0.3.0
|
@ -1,23 +1,22 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { NotFoundComponent } from "src/app/components/error/not-found/not-found.component";
|
import { NotFoundComponent } from 'src/app/components/error/not-found/not-found.component';
|
||||||
import { AuthGuard } from "src/app/core/guard/auth.guard";
|
import { AuthGuard } from 'src/app/core/guard/auth.guard';
|
||||||
import { HomeComponent } from "src/app/components/home/home.component";
|
import { HomeComponent } from 'src/app/components/home/home.component';
|
||||||
import { RedirectComponent } from "src/app/components/redirect/redirect.component";
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: "",
|
path: '',
|
||||||
component: HomeComponent,
|
component: HomeComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "admin",
|
path: 'admin',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import("./modules/admin/admin.module").then((m) => m.AdminModule),
|
import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
{ path: "404", component: NotFoundComponent },
|
{ path: '404', component: NotFoundComponent },
|
||||||
{ path: "**", component: RedirectComponent },
|
{ path: '**', redirectTo: '/404', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
import { APP_INITIALIZER, ErrorHandler, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from "@angular/platform-browser";
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { AppRoutingModule } from "./app-routing.module";
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from "./app.component";
|
import { AppComponent } from './app.component';
|
||||||
import { KeycloakService } from "keycloak-angular";
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { initializeKeycloak } from "./core/init-keycloak";
|
import { initializeKeycloak } from './core/init-keycloak';
|
||||||
import { HttpClient } from "@angular/common/http";
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { environment } from "../environments/environment";
|
import { environment } from '../environments/environment';
|
||||||
import { FooterComponent } from "src/app/components/footer/footer.component";
|
import { FooterComponent } from 'src/app/components/footer/footer.component';
|
||||||
import { HeaderComponent } from "src/app/components/header/header.component";
|
import { HeaderComponent } from 'src/app/components/header/header.component';
|
||||||
import { NotFoundComponent } from "src/app/components/error/not-found/not-found.component";
|
import { NotFoundComponent } from 'src/app/components/error/not-found/not-found.component';
|
||||||
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
import { Logger } from "src/app/service/logger.service";
|
import { Logger } from 'src/app/service/logger.service';
|
||||||
import { SharedModule } from "src/app/modules/shared/shared.module";
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
import { SpinnerComponent } from "src/app/components/spinner/spinner.component";
|
import { SpinnerComponent } from 'src/app/components/spinner/spinner.component';
|
||||||
import { ConfirmationService, MessageService } from "primeng/api";
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
import { DialogService } from "primeng/dynamicdialog";
|
import { DialogService } from 'primeng/dynamicdialog';
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { SidebarComponent } from "./components/sidebar/sidebar.component";
|
import { SidebarComponent } from './components/sidebar/sidebar.component';
|
||||||
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||||
import { HomeComponent } from "./components/home/home.component";
|
import { HomeComponent } from './components/home/home.component';
|
||||||
import { RedirectComponent } from "./components/redirect/redirect.component";
|
import { SettingsService } from 'src/app/service/settings.service';
|
||||||
import { SettingsService } from "src/app/service/settings.service";
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Logger.enableProductionMode();
|
Logger.enableProductionMode();
|
||||||
@ -36,7 +35,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||||||
|
|
||||||
export function appInitializerFactory(
|
export function appInitializerFactory(
|
||||||
keycloak: KeycloakService,
|
keycloak: KeycloakService,
|
||||||
settings: SettingsService,
|
settings: SettingsService
|
||||||
): () => Promise<void> {
|
): () => Promise<void> {
|
||||||
return (): Promise<void> =>
|
return (): Promise<void> =>
|
||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
@ -44,7 +43,7 @@ export function appInitializerFactory(
|
|||||||
.loadSettings()
|
.loadSettings()
|
||||||
.then(() => initializeKeycloak(keycloak, settings))
|
.then(() => initializeKeycloak(keycloak, settings))
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
.catch((error) => reject(error));
|
.catch(error => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +56,6 @@ export function appInitializerFactory(
|
|||||||
SpinnerComponent,
|
SpinnerComponent,
|
||||||
SidebarComponent,
|
SidebarComponent,
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
RedirectComponent,
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -65,7 +63,7 @@ export function appInitializerFactory(
|
|||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
defaultLanguage: "en",
|
defaultLanguage: 'en',
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
useFactory: HttpLoaderFactory,
|
useFactory: HttpLoaderFactory,
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<div class="flex items-center justify-center">
|
|
||||||
<div class="relative w-screen h-screen bg-cover bg-center"
|
|
||||||
style="background-image: url({{settings.loadingScreen.background}})"></div>
|
|
||||||
|
|
||||||
<div class="absolute w-1/3 h-2/5 rounded-xl p-5 flex flex-col gap-5 justify-center items-center">
|
|
||||||
<div class="absolute inset-0 bg-black opacity-70 rounded-xl"></div>
|
|
||||||
<div class="relative logo flex justify-center items-center">
|
|
||||||
<img class="h-48"
|
|
||||||
[src]="settings.loadingScreen.logo"
|
|
||||||
alt="logo">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 class="relative text-3xl text-white">Redirecting...</h1>
|
|
||||||
<p class="relative text-white" *ngIf="secs > -1">You will be redirected in {{ secs }} seconds.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||||||
.bg-image {
|
|
||||||
background-position: top center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
||||||
|
|
||||||
import { RedirectComponent } from "./redirect.component";
|
|
||||||
|
|
||||||
describe("RedirectComponent", () => {
|
|
||||||
let component: RedirectComponent;
|
|
||||||
let fixture: ComponentFixture<RedirectComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [RedirectComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(RedirectComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create", () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,69 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { SidebarService } from 'src/app/service/sidebar.service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { ShortUrlDto } from 'src/app/model/entities/short-url';
|
|
||||||
import { SettingsService } from 'src/app/service/settings.service';
|
|
||||||
import { AppSettings } from 'src/app/model/config/app-settings';
|
|
||||||
import { GuiService } from 'src/app/service/gui.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-redirect',
|
|
||||||
templateUrl: './redirect.component.html',
|
|
||||||
styleUrl: './redirect.component.scss',
|
|
||||||
})
|
|
||||||
export class RedirectComponent implements OnInit {
|
|
||||||
defaultSecs = 5;
|
|
||||||
secs = -1;
|
|
||||||
|
|
||||||
settings: AppSettings = this.settingsService.settings;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private sidebar: SidebarService,
|
|
||||||
private router: Router,
|
|
||||||
private http: HttpClient,
|
|
||||||
private settingsService: SettingsService,
|
|
||||||
private gui: GuiService
|
|
||||||
) {
|
|
||||||
this.sidebar.hide();
|
|
||||||
this.gui.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.handleUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUrl() {
|
|
||||||
let shortUrl = this.router.url;
|
|
||||||
if (shortUrl === '/') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shortUrl.startsWith('/')) {
|
|
||||||
shortUrl = shortUrl.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.http
|
|
||||||
.get<ShortUrlDto | undefined>(`${this.settings.api.url}/find/${shortUrl}`)
|
|
||||||
.subscribe(async response => {
|
|
||||||
if (!response) {
|
|
||||||
await this.router.navigate(['/404']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.loadingScreen) {
|
|
||||||
window.location.href = `${this.settings.api.url}/redirect/${shortUrl}`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.secs = this.defaultSecs;
|
|
||||||
setInterval(() => {
|
|
||||||
this.secs--;
|
|
||||||
|
|
||||||
if (this.secs === 0) {
|
|
||||||
window.location.href = `${this.settings.api.url}/redirect/${shortUrl}`;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Theme } from "src/app/model/view/theme";
|
import { Theme } from 'src/app/model/view/theme';
|
||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
termsUrl: string;
|
termsUrl: string;
|
||||||
@ -23,4 +23,5 @@ export interface KeycloakSettings {
|
|||||||
|
|
||||||
export interface ApiSettings {
|
export interface ApiSettings {
|
||||||
url: string;
|
url: string;
|
||||||
|
redirector: string;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-url
|
|||||||
import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
|
import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { AuthService } from 'src/app/service/auth.service';
|
||||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||||
|
import { SettingsService } from 'src/app/service/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-short-urls',
|
selector: 'app-short-urls',
|
||||||
@ -43,7 +44,8 @@ export class ShortUrlsPage
|
|||||||
constructor(
|
constructor(
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private confirmation: ConfirmationDialogService,
|
private confirmation: ConfirmationDialogService,
|
||||||
private auth: AuthService
|
private auth: AuthService,
|
||||||
|
private settings: SettingsService
|
||||||
) {
|
) {
|
||||||
super(true, {
|
super(true, {
|
||||||
read: [PermissionsEnum.shortUrls],
|
read: [PermissionsEnum.shortUrls],
|
||||||
@ -137,13 +139,15 @@ export class ShortUrlsPage
|
|||||||
}
|
}
|
||||||
|
|
||||||
open(url: string) {
|
open(url: string) {
|
||||||
window.open(url, '_blank');
|
window.open(`${this.settings.settings.api.redirector}/${url}`, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(val: string) {
|
copy(val: string) {
|
||||||
navigator.clipboard.writeText(`${window.origin}/${val}`).then(() => {
|
navigator.clipboard
|
||||||
this.toast.info('common.copied', 'common.copied_to_clipboard');
|
.writeText(`${this.settings.settings.api.redirector}/${val}`)
|
||||||
});
|
.then(() => {
|
||||||
|
this.toast.info('common.copied', 'common.copied_to_clipboard');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getShortUrlsWithoutGroup(): ShortUrl[] {
|
getShortUrlsWithoutGroup(): ShortUrl[] {
|
||||||
|
@ -9,15 +9,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"api": {
|
"api": {
|
||||||
"url": ""
|
"url": "",
|
||||||
|
"redirector": ""
|
||||||
},
|
},
|
||||||
"keycloak": {
|
"keycloak": {
|
||||||
"url": "",
|
"url": "",
|
||||||
"realm": "",
|
"realm": "",
|
||||||
"clientId": ""
|
"clientId": ""
|
||||||
},
|
|
||||||
"loadingScreen": {
|
|
||||||
"background": "https://www.sh-edraft.de/wp-content/uploads/2020/03/IMG_20170731_213039-scaled.jpg",
|
|
||||||
"logo": "https://www.sh-edraft.de/wp-content/uploads/2020/04/klein_transparent-e1735827600932.png"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user