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