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.environment import Environment
|
||||
from core.logger import DBLogger
|
||||
|
||||
logger = DBLogger(__name__)
|
||||
|
||||
|
||||
class Database:
|
||||
@ -14,3 +19,30 @@ class Database:
|
||||
@classmethod
|
||||
def connect(cls):
|
||||
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:
|
||||
@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
|
||||
async def _seed_data():
|
||||
@ -69,7 +45,7 @@ class Startup:
|
||||
|
||||
app.debug = Environment.get_environment() == "development"
|
||||
|
||||
await cls._startup_db()
|
||||
await Database.startup_db()
|
||||
await FileService.clean_files()
|
||||
|
||||
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 { RouterModule, Routes } from "@angular/router";
|
||||
import { NotFoundComponent } from "src/app/components/error/not-found/not-found.component";
|
||||
import { AuthGuard } from "src/app/core/guard/auth.guard";
|
||||
import { HomeComponent } from "src/app/components/home/home.component";
|
||||
import { RedirectComponent } from "src/app/components/redirect/redirect.component";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { NotFoundComponent } from 'src/app/components/error/not-found/not-found.component';
|
||||
import { AuthGuard } from 'src/app/core/guard/auth.guard';
|
||||
import { HomeComponent } from 'src/app/components/home/home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
path: '',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
path: "admin",
|
||||
path: 'admin',
|
||||
loadChildren: () =>
|
||||
import("./modules/admin/admin.module").then((m) => m.AdminModule),
|
||||
import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{ path: "404", component: NotFoundComponent },
|
||||
{ path: "**", component: RedirectComponent },
|
||||
{ path: '404', component: NotFoundComponent },
|
||||
{ path: '**', redirectTo: '/404', pathMatch: 'full' },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -1,28 +1,27 @@
|
||||
import { APP_INITIALIZER, ErrorHandler, NgModule } from "@angular/core";
|
||||
import { BrowserModule } from "@angular/platform-browser";
|
||||
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppRoutingModule } from "./app-routing.module";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { KeycloakService } from "keycloak-angular";
|
||||
import { initializeKeycloak } from "./core/init-keycloak";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { environment } from "../environments/environment";
|
||||
import { FooterComponent } from "src/app/components/footer/footer.component";
|
||||
import { HeaderComponent } from "src/app/components/header/header.component";
|
||||
import { NotFoundComponent } from "src/app/components/error/not-found/not-found.component";
|
||||
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
||||
import { Logger } from "src/app/service/logger.service";
|
||||
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||
import { SpinnerComponent } from "src/app/components/spinner/spinner.component";
|
||||
import { ConfirmationService, MessageService } from "primeng/api";
|
||||
import { DialogService } from "primeng/dynamicdialog";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { SidebarComponent } from "./components/sidebar/sidebar.component";
|
||||
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||
import { HomeComponent } from "./components/home/home.component";
|
||||
import { RedirectComponent } from "./components/redirect/redirect.component";
|
||||
import { SettingsService } from "src/app/service/settings.service";
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { initializeKeycloak } from './core/init-keycloak';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../environments/environment';
|
||||
import { FooterComponent } from 'src/app/components/footer/footer.component';
|
||||
import { HeaderComponent } from 'src/app/components/header/header.component';
|
||||
import { NotFoundComponent } from 'src/app/components/error/not-found/not-found.component';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { Logger } from 'src/app/service/logger.service';
|
||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||
import { SpinnerComponent } from 'src/app/components/spinner/spinner.component';
|
||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||
import { DialogService } from 'primeng/dynamicdialog';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { SidebarComponent } from './components/sidebar/sidebar.component';
|
||||
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
import { SettingsService } from 'src/app/service/settings.service';
|
||||
|
||||
if (environment.production) {
|
||||
Logger.enableProductionMode();
|
||||
@ -36,7 +35,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
||||
|
||||
export function appInitializerFactory(
|
||||
keycloak: KeycloakService,
|
||||
settings: SettingsService,
|
||||
settings: SettingsService
|
||||
): () => Promise<void> {
|
||||
return (): Promise<void> =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
@ -44,7 +43,7 @@ export function appInitializerFactory(
|
||||
.loadSettings()
|
||||
.then(() => initializeKeycloak(keycloak, settings))
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
@ -57,7 +56,6 @@ export function appInitializerFactory(
|
||||
SpinnerComponent,
|
||||
SidebarComponent,
|
||||
HomeComponent,
|
||||
RedirectComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -65,7 +63,7 @@ export function appInitializerFactory(
|
||||
AppRoutingModule,
|
||||
SharedModule,
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: "en",
|
||||
defaultLanguage: 'en',
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
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 {
|
||||
termsUrl: string;
|
||||
@ -23,4 +23,5 @@ export interface KeycloakSettings {
|
||||
|
||||
export interface ApiSettings {
|
||||
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 { AuthService } from 'src/app/service/auth.service';
|
||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||
import { SettingsService } from 'src/app/service/settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-short-urls',
|
||||
@ -43,7 +44,8 @@ export class ShortUrlsPage
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
private confirmation: ConfirmationDialogService,
|
||||
private auth: AuthService
|
||||
private auth: AuthService,
|
||||
private settings: SettingsService
|
||||
) {
|
||||
super(true, {
|
||||
read: [PermissionsEnum.shortUrls],
|
||||
@ -137,13 +139,15 @@ export class ShortUrlsPage
|
||||
}
|
||||
|
||||
open(url: string) {
|
||||
window.open(url, '_blank');
|
||||
window.open(`${this.settings.settings.api.redirector}/${url}`, '_blank');
|
||||
}
|
||||
|
||||
copy(val: string) {
|
||||
navigator.clipboard.writeText(`${window.origin}/${val}`).then(() => {
|
||||
this.toast.info('common.copied', 'common.copied_to_clipboard');
|
||||
});
|
||||
navigator.clipboard
|
||||
.writeText(`${this.settings.settings.api.redirector}/${val}`)
|
||||
.then(() => {
|
||||
this.toast.info('common.copied', 'common.copied_to_clipboard');
|
||||
});
|
||||
}
|
||||
|
||||
getShortUrlsWithoutGroup(): ShortUrl[] {
|
||||
|
@ -9,15 +9,12 @@
|
||||
}
|
||||
],
|
||||
"api": {
|
||||
"url": ""
|
||||
"url": "",
|
||||
"redirector": ""
|
||||
},
|
||||
"keycloak": {
|
||||
"url": "",
|
||||
"realm": "",
|
||||
"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