mirror of
https://github.com/fergalmoran/snapp.git
synced 2026-01-06 08:55:02 +00:00
updated importer
This commit is contained in:
@@ -12,7 +12,7 @@ Are you looking for a reliable solution for self-hosted URL shortening? Look no
|
||||
- **Usage Analytics:** Empower yourself with detailed analytics for every link you create. Snapp gathers metrics anonymously, providing insights into link engagements.
|
||||
- **Extend Metrics:** Integrate your Snapp Instance with your self-hosted or cloud Umami Analytics instance for advanced metrics of your Snapp.
|
||||
- **Check URL Reputation:** Secure the links passing through your Snapp instance with a check on VirusTotal API reputation.
|
||||
- **REST API:** Community requested features that enable REST API endpoints to create and manage your Snapps remotely. Read all Swagger Docs [here](https://labs.snapp.li/dashboard/docs).
|
||||
- **REST API:** Community requested features that enable REST API endpoints to create and manage your Snapps remotely. Read all Swagger Docs [here](https://snapp.li/dashboard/docs).
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -121,4 +121,4 @@ The technology involved:
|
||||
- Auth.js
|
||||
- Skeleton
|
||||
- MaxMind
|
||||
- Lucide
|
||||
- Lucide
|
||||
823
package-lock.json
generated
823
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -72,6 +72,7 @@
|
||||
"qrcode": "^1.5.3",
|
||||
"redis": "^4.6.12",
|
||||
"redis-om": "^0.4.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"svelte-sonner": "^0.3.7",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-variants": "^0.1.20",
|
||||
|
||||
BIN
prisma/db.sqlite
Normal file
BIN
prisma/db.sqlite
Normal file
Binary file not shown.
@@ -85,19 +85,30 @@ const authHandler = SvelteKitAuth({
|
||||
});
|
||||
|
||||
async function initializeDBIndexes() {
|
||||
|
||||
try {
|
||||
await db.users.createIndex();
|
||||
await db.apikeys.createIndex();
|
||||
await db.snapps.createIndex();
|
||||
await db.usages.createIndex();
|
||||
|
||||
await check_and_set('settings:app:limits:enabled', env.ENABLE_LIMITS?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:limits:max:urls', env.MAX_SHORT_URL?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:limits:max:usages', env.MAX_USAGES?.toString()?.toLowerCase());
|
||||
await check_and_set(
|
||||
'settings:app:limits:enabled',
|
||||
env.ENABLE_LIMITS?.toString()?.toLowerCase()
|
||||
);
|
||||
await check_and_set(
|
||||
'settings:app:limits:max:urls',
|
||||
env.MAX_SHORT_URL?.toString()?.toLowerCase()
|
||||
);
|
||||
await check_and_set(
|
||||
'settings:app:limits:max:usages',
|
||||
env.MAX_USAGES?.toString()?.toLowerCase()
|
||||
);
|
||||
await check_and_set('settings:app:limits:max:rpm', env.MAX_RPM?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:limits:max:rpd', env.MAX_RPD?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:signup:enabled', env.ENABLE_SIGNUP?.toString()?.toLowerCase());
|
||||
await check_and_set(
|
||||
'settings:app:signup:enabled',
|
||||
env.ENABLE_SIGNUP?.toString()?.toLowerCase()
|
||||
);
|
||||
await check_and_set('settings:app:home:enabled', env.ENABLE_HOME?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:smtp:host', env.SMTP_HOST?.toString()?.toLowerCase());
|
||||
await check_and_set('settings:app:smtp:pass', env.SMTP_PASSWORD?.toString()?.toLowerCase());
|
||||
@@ -115,7 +126,7 @@ async function initializeDBIndexes() {
|
||||
}
|
||||
}
|
||||
|
||||
async function check_and_set(setting: string, value: string | null) {
|
||||
async function check_and_set(setting: string, value: string | null | undefined) {
|
||||
const exists = await db.getSetting(setting);
|
||||
if (!exists && value) await db.setSetting(setting, value);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ import type SnappError from '$lib/db/utils/snappError.js';
|
||||
import { redirect, fail } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ locals, fetch }) {
|
||||
const session = await locals.getSession();
|
||||
|
||||
const { active_smtp } = await (await fetch('/api/smtp/test')).json();
|
||||
|
||||
if (session) throw redirect(302, '/');
|
||||
|
||||
return { active_smtp };
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { db } from '$lib/db/index.js';
|
||||
import jsonify from '$lib/utils/jsonify/index.js';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export async function load({ locals }) {
|
||||
const session = await locals.getSession();
|
||||
@@ -9,11 +10,15 @@ export async function load({ locals }) {
|
||||
const is_admin = await db.admin(session.user.id);
|
||||
|
||||
const user = await db.users.search().where('id').equalTo(session.user.id).first();
|
||||
const users = is_admin ? await db.users.search().where('id').not.equalTo(session.user.id).returnAll() : [];
|
||||
|
||||
const users = is_admin
|
||||
? await db.users.search().where('id').not.equalTo(session.user.id).returnAll()
|
||||
: [];
|
||||
const filepath = process.cwd() + '/prisma/db.sqlite';
|
||||
const oldDB = existsSync(filepath);
|
||||
return {
|
||||
users: jsonify(users) as DBUser[],
|
||||
user: jsonify(user!) as DBUser
|
||||
user: jsonify(user!) as DBUser,
|
||||
has_sqlite: oldDB
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import Breadcrumbs from '$lib/ui/crumbs/breadcrumbs.svelte';
|
||||
import { H3, Lead, Paragraph, Small } from '$lib/ui/typography';
|
||||
import { FileDropzone } from '@skeletonlabs/skeleton';
|
||||
import { FileDropzone, getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import CustomToast from '$lib/ui/toaster/customToast.svelte';
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
let imported_snapps: Partial<DBSnapp>[] = [];
|
||||
|
||||
let main_author = data.user.id;
|
||||
let main_author = data.user?.id;
|
||||
type OldSnapp = {
|
||||
id: string;
|
||||
original_url: string;
|
||||
@@ -121,32 +121,72 @@
|
||||
});
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
imported_snapps = [];
|
||||
files=undefined
|
||||
imported_snapps = [];
|
||||
files = undefined;
|
||||
};
|
||||
};
|
||||
|
||||
function handle_submit() {
|
||||
document.forms.namedItem('import')?.requestSubmit();
|
||||
}
|
||||
|
||||
async function importFromOldDB() {
|
||||
const modal = {
|
||||
type: 'confirm',
|
||||
// Data
|
||||
title: $t('snapps:import:sqlite:modal:label'),
|
||||
body: $t('snapps:import:sqlite:modal:helper'),
|
||||
buttonTextConfirm: $t('global:misc:confirm'),
|
||||
buttonTextCancel: $t('global:misc:cancel'),
|
||||
// TRUE if confirm pressed, FALSE if cancel pressed
|
||||
response: async (r: boolean) => {
|
||||
if (r === true) {
|
||||
const rows: OldSnapp[] = (await (await data.fetch('/api/upgrade')).json()) as OldSnapp[];
|
||||
|
||||
rows.map((snapp) => {
|
||||
const _snapp = {
|
||||
id: snapp.id,
|
||||
created: new Date(snapp.created_at),
|
||||
user_id: data.user.id,
|
||||
shortcode: snapp.short_code,
|
||||
has_secret: snapp.has_secret,
|
||||
secret: snapp.secret,
|
||||
original_url: snapp.original_url
|
||||
};
|
||||
|
||||
imported_snapps = [...imported_snapps, _snapp];
|
||||
});
|
||||
}
|
||||
}
|
||||
} satisfies ModalSettings;
|
||||
|
||||
modalStore.trigger(modal);
|
||||
}
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{$t('global:appname')} | {$t('snapps:import')}</title></svelte:head>
|
||||
|
||||
<form method="post" use:enhance={enhanceImport} id="import" />
|
||||
<div class="page h-full flex">
|
||||
<div class="flex max-h-max">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<Breadcrumbs
|
||||
urls={[
|
||||
{ label: $t('global:pages:dashboard'), href: '/dashboard' },
|
||||
{ label: $t('snapps:import') }
|
||||
]}
|
||||
/>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="flex gap-2 items-center w-full">
|
||||
<LinkIcon class="w-6 h-6" />
|
||||
<H3 class="mb-1 w-max">
|
||||
{$t('global:sections:import')}
|
||||
</H3>
|
||||
{#if data.has_sqlite}
|
||||
<button class="btn variant-outline-primary ms-auto" on:click={importFromOldDB}>
|
||||
<Small class="font-semibold">{$t('snapps:import:sqlite:button')}</Small>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
3
src/routes/(frontend)/dashboard/urls/import/+page.ts
Normal file
3
src/routes/(frontend)/dashboard/urls/import/+page.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function load({data,fetch}){
|
||||
return {...data, fetch}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { db } from '$lib/db/index.js';
|
||||
import jsonify from '$lib/utils/jsonify/index.js';
|
||||
|
||||
export async function load({ locals, depends, fetch }) {
|
||||
export async function load({ locals, depends, fetch, cookies }) {
|
||||
depends('snapp:main');
|
||||
const session = await locals.getSession();
|
||||
|
||||
const user =
|
||||
session && (await db.users.search().where('id').equal(session?.user.id).returnFirst());
|
||||
|
||||
const theme = locals.theme;
|
||||
const lang = locals.lang;
|
||||
const { localization, locales, languages } = (await (
|
||||
@@ -18,7 +22,7 @@ export async function load({ locals, depends, fetch }) {
|
||||
};
|
||||
const max_urls = await db.getSetting('settings:app:limits:max:urls');
|
||||
const version = env.SNAPP_VERSION;
|
||||
const isAdmin = session ? await db.admin(session.user.id) === true : false;
|
||||
const isAdmin = session ? (await db.admin(session.user.id)) === true : false;
|
||||
return {
|
||||
session,
|
||||
theme,
|
||||
@@ -28,6 +32,7 @@ export async function load({ locals, depends, fetch }) {
|
||||
languages,
|
||||
appversion: version,
|
||||
max_urls,
|
||||
isAdmin
|
||||
isAdmin,
|
||||
user: user !== null ? jsonify(user) : null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export function load({ data, fetch }) {
|
||||
import { browser } from '$app/environment';
|
||||
import { signOut } from '@auth/sveltekit/client';
|
||||
|
||||
export async function load({ data, fetch }) {
|
||||
if (browser && data.session && data.user === null) await signOut();
|
||||
|
||||
return { ...data, fetch };
|
||||
}
|
||||
|
||||
50
src/routes/api/upgrade/+server.ts
Normal file
50
src/routes/api/upgrade/+server.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { db } from '$lib/db';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { existsSync } from 'fs';
|
||||
import sqlite3 from 'sqlite3';
|
||||
|
||||
function createDbConnection(filepath: string) {
|
||||
const db = new sqlite3.Database(filepath, (error) => {
|
||||
if (error) throw error;
|
||||
});
|
||||
console.log('Connection with SQLite has been established');
|
||||
return db;
|
||||
}
|
||||
|
||||
type OldSnapp = {
|
||||
id: string;
|
||||
original_url: string;
|
||||
short_code: string;
|
||||
created_at: Date;
|
||||
expires_at: Date | null;
|
||||
secret: string | null;
|
||||
has_secret: boolean;
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
async function selectSnapps(db: sqlite3.Database) {
|
||||
return new Promise<OldSnapp[]>((resolve, reject) => {
|
||||
db.all<OldSnapp>('SELECT * FROM Snapp', (error, rows) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
export async function GET() {
|
||||
const filepath = process.cwd() + '/prisma/db.sqlite';
|
||||
|
||||
const dbSqlite = existsSync(filepath);
|
||||
if (!dbSqlite)
|
||||
return json({
|
||||
message: 'This instance has no prisma db.sqlite included'
|
||||
});
|
||||
|
||||
const _db = createDbConnection(filepath);
|
||||
const rows = await selectSnapps(_db);
|
||||
if (rows && rows.length > 0) return json(rows);
|
||||
else return json({ message: 'No row found, are you sure?', rows });
|
||||
}
|
||||
@@ -340,6 +340,9 @@
|
||||
"snapps:vt:api:key:malicious": "This url has been blacklisted by VirusTotal API. If you think this is a mistake, please contact System Administrator.",
|
||||
|
||||
"snapps:import": "Import Snapps",
|
||||
"snapps:import:sqlite:button": "Upgrade Database",
|
||||
"snapps:import:sqlite:modal:label": "Upgrade Database",
|
||||
"snapps:import:sqlite:modal:helper":"A SQLITE file from a previous version has been detected. Do you want to try importing the previous Snapp? This operation may take a long time for large databases.",
|
||||
"snapps:import:helper": "Import CSV File from previous version of Snapp",
|
||||
"snapps:import:label": "Upload a CSV File",
|
||||
"snapps:import:label:assign": "Assign snapps to users",
|
||||
@@ -428,6 +431,6 @@
|
||||
"home:migration": "Migration",
|
||||
"home:migration:description": "Latest versions of Snapp included CSV Export in order to facilitate migration. Simply login and import your urls from dashboard, and continue from where you left.",
|
||||
|
||||
"home:stack:label":"The Stack",
|
||||
"home:stack:description":"The technology involved"
|
||||
"home:stack:label": "The Stack",
|
||||
"home:stack:description": "The technology involved"
|
||||
}
|
||||
|
||||
@@ -342,6 +342,9 @@
|
||||
"snapps:vt:api:key:malicious": "Questo URL è stato inserito nella blacklist dall'API VirusTotal. Se pensi che ciò sia un errore, contatta l'amministratore di sistema.",
|
||||
|
||||
"snapps:import": "Importa Snapps",
|
||||
"snapps:import:sqlite:button": "Aggiorna Database",
|
||||
"snapps:import:sqlite:modal:label":"Aggiorna Database",
|
||||
"snapps:import:sqlite:modal:helper":"Un file SQLITE di una precedente versione è stato rilevato. Vuoi provare l'importazione dei precedenti Snapp? Questa operazione potrebbe richiedere molto tempo in caso di database corposi",
|
||||
"snapps:import:helper": "Importa il CSV di una versione precedente di Snapp",
|
||||
"snapps:import:label": "Carica un CSV",
|
||||
"snapps:import:label:assign": "Assegna snapps agli utenti",
|
||||
|
||||
Reference in New Issue
Block a user