Fix installer, add favicon, improve editor and layout
This commit is contained in:
parent
ea90efc902
commit
75556045c9
@ -1,11 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Surafino Installer ==="
|
||||
echo "=============================="
|
||||
echo " Surafino Installer"
|
||||
echo "=============================="
|
||||
|
||||
# -------- INPUT --------
|
||||
read -p "Domain (example.com): " DOMAIN
|
||||
read -p "DB Host: " DB_HOST
|
||||
read -p "DB Host (127.0.0.1): " DB_HOST
|
||||
DB_HOST=${DB_HOST:-127.0.0.1}
|
||||
|
||||
read -p "DB Port (3306): " DB_PORT
|
||||
DB_PORT=${DB_PORT:-3306}
|
||||
|
||||
read -p "DB User: " DB_USER
|
||||
read -sp "DB Password: " DB_PASSWORD
|
||||
echo
|
||||
@ -13,37 +20,44 @@ read -p "DB Name: " DB_NAME
|
||||
|
||||
JWT_SECRET=$(openssl rand -hex 32)
|
||||
|
||||
echo "Installing system packages..."
|
||||
echo "== Installing system packages =="
|
||||
apt update
|
||||
apt install -y nginx certbot python3-certbot-nginx mariadb-client nodejs npm
|
||||
apt install -y nginx certbot python3-certbot-nginx mariadb-client curl ca-certificates
|
||||
|
||||
echo "Installing node dependencies..."
|
||||
echo "== Installing Node.js 20 LTS =="
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt install -y nodejs
|
||||
|
||||
echo "== Node version =="
|
||||
node -v
|
||||
npm -v
|
||||
|
||||
echo "== Installing dependencies =="
|
||||
npm install
|
||||
|
||||
echo "== Building app =="
|
||||
npm run build
|
||||
|
||||
echo "Creating .env..."
|
||||
echo "== Creating .env =="
|
||||
cat > .env <<EOF
|
||||
DB_HOST=$DB_HOST
|
||||
DB_PORT=$DB_PORT
|
||||
DB_USER=$DB_USER
|
||||
DB_PASSWORD=$DB_PASSWORD
|
||||
DB_NAME=$DB_NAME
|
||||
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
APP_URL=https://$DOMAIN
|
||||
EOF
|
||||
|
||||
echo "Setting up database..."
|
||||
echo "== Setting up database =="
|
||||
sed \
|
||||
-e "s/{{DB_NAME}}/$DB_NAME/g" \
|
||||
installer/database.sql > /tmp/db.sql
|
||||
installer/database.sql > /tmp/surafino.sql
|
||||
|
||||
mysql \
|
||||
-h $DB_HOST \
|
||||
-P $DB_PORT \
|
||||
-u $DB_USER \
|
||||
-p$DB_PASSWORD < /tmp/db.sql
|
||||
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" < /tmp/surafino.sql
|
||||
|
||||
echo "Configuring nginx..."
|
||||
echo "== Configuring NGINX =="
|
||||
sed \
|
||||
-e "s/{{DOMAIN}}/$DOMAIN/g" \
|
||||
installer/nginx.conf.template > /etc/nginx/sites-available/surafino
|
||||
@ -52,17 +66,19 @@ ln -sf /etc/nginx/sites-available/surafino /etc/nginx/sites-enabled/surafino
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
echo "Installing PM2..."
|
||||
echo "== Installing PM2 =="
|
||||
npm install -g pm2
|
||||
pm2 start npm --name surafino -- start
|
||||
pm2 save
|
||||
pm2 startup systemd -u root --hp /root
|
||||
|
||||
echo "Requesting SSL certificate..."
|
||||
certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m admin@$DOMAIN
|
||||
echo "== Installing SSL (Let's Encrypt) =="
|
||||
certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos -m admin@"$DOMAIN" || true
|
||||
|
||||
echo "=== INSTALLATION COMPLETE ==="
|
||||
echo "Admin login:"
|
||||
echo " username: admin"
|
||||
echo " password: admin"
|
||||
echo "Please change password immediately!"
|
||||
echo "=============================="
|
||||
echo " INSTALLATION COMPLETE "
|
||||
echo "=============================="
|
||||
echo " Admin login:"
|
||||
echo " username: admin"
|
||||
echo " password: admin"
|
||||
echo " !!! CHANGE PASSWORD IMMEDIATELY !!!"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.8 KiB |
@ -1,5 +1,11 @@
|
||||
import "./globals.css";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Surafino",
|
||||
description: "Surafino blog & news platform",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
@ -8,9 +14,9 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<body className="min-h-screen bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
||||
<Navbar />
|
||||
{children}
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -6,30 +6,35 @@ import Image from "@tiptap/extension-image";
|
||||
import Link from "@tiptap/extension-link";
|
||||
import { Video } from "@/components/extensions/Video";
|
||||
|
||||
export default function Editor({
|
||||
content,
|
||||
onChange,
|
||||
}: {
|
||||
type Props = {
|
||||
content: string;
|
||||
onChange: (html: string) => void;
|
||||
}) {
|
||||
};
|
||||
|
||||
export default function Editor({ content, onChange }: Props) {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Image,
|
||||
Link.configure({ openOnClick: false }),
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
}),
|
||||
Video,
|
||||
],
|
||||
content,
|
||||
immediatelyRender: false, // Next.js 16 SSR fix
|
||||
immediatelyRender: false, // SSR fix for Next 16
|
||||
onUpdate({ editor }) {
|
||||
onChange(editor.getHTML());
|
||||
},
|
||||
});
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
async function upload(file: File) {
|
||||
/* =========================
|
||||
UPLOAD HELPER
|
||||
========================= */
|
||||
async function upload(file: File): Promise<{
|
||||
url: string;
|
||||
type: "image" | "video";
|
||||
}> {
|
||||
const data = new FormData();
|
||||
data.append("file", file);
|
||||
|
||||
@ -42,18 +47,30 @@ export default function Editor({
|
||||
throw new Error("Upload failed");
|
||||
}
|
||||
|
||||
return res.json() as Promise<{
|
||||
url: string;
|
||||
type: "image" | "video";
|
||||
}>;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/* =========================
|
||||
ADD IMAGE
|
||||
========================= */
|
||||
async function addImage(file: File) {
|
||||
if (!editor) return;
|
||||
|
||||
const { url } = await upload(file);
|
||||
editor.chain().focus().setImage({ src: url }).run();
|
||||
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setImage({ src: url })
|
||||
.run();
|
||||
}
|
||||
|
||||
/* =========================
|
||||
ADD VIDEO
|
||||
========================= */
|
||||
async function addVideo(file: File) {
|
||||
if (!editor) return;
|
||||
|
||||
const { url } = await upload(file);
|
||||
|
||||
editor
|
||||
@ -67,14 +84,26 @@ export default function Editor({
|
||||
type: file.type,
|
||||
},
|
||||
},
|
||||
{ type: "paragraph" },
|
||||
{
|
||||
type: "paragraph",
|
||||
},
|
||||
])
|
||||
.run();
|
||||
}
|
||||
|
||||
if (!editor) {
|
||||
return (
|
||||
<div className="border rounded-lg p-4 text-gray-500">
|
||||
Loading editor…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg bg-white dark:bg-gray-900">
|
||||
{/* TOOLBAR */}
|
||||
{/* =========================
|
||||
TOOLBAR
|
||||
========================= */}
|
||||
<div
|
||||
className="
|
||||
flex flex-wrap gap-2 p-2 border-b
|
||||
@ -84,7 +113,9 @@ export default function Editor({
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleBold().run()
|
||||
}
|
||||
className="editor-btn"
|
||||
>
|
||||
Bold
|
||||
@ -92,7 +123,9 @@ export default function Editor({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleItalic().run()
|
||||
}
|
||||
className="editor-btn"
|
||||
>
|
||||
Italic
|
||||
@ -101,7 +134,11 @@ export default function Editor({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleHeading({ level: 2 })
|
||||
.run()
|
||||
}
|
||||
className="editor-btn"
|
||||
>
|
||||
@ -115,7 +152,8 @@ export default function Editor({
|
||||
hidden
|
||||
accept="image/*"
|
||||
onChange={e =>
|
||||
e.target.files && addImage(e.target.files[0])
|
||||
e.target.files &&
|
||||
addImage(e.target.files[0])
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
@ -127,13 +165,16 @@ export default function Editor({
|
||||
hidden
|
||||
accept="video/mp4,video/webm"
|
||||
onChange={e =>
|
||||
e.target.files && addVideo(e.target.files[0])
|
||||
e.target.files &&
|
||||
addVideo(e.target.files[0])
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
{/* =========================
|
||||
CONTENT
|
||||
========================= */}
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className="
|
||||
|
||||
Loading…
Reference in New Issue
Block a user