Compare commits
10 Commits
0617bf2a8f
...
253e4def43
| Author | SHA1 | Date | |
|---|---|---|---|
| 253e4def43 | |||
| 2c6b24b0fd | |||
| 60b4e07f0c | |||
| e7c904069a | |||
| e25768d120 | |||
| cc1e8e3af0 | |||
| 02fb86049d | |||
| 6ed054d56a | |||
| 1acb32aea0 | |||
| b42542931a |
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 📜 SkazaNull — Пацанский цитатник
|
||||||
|
|
||||||
|
Простенькая прилажка для хранения забавных (и не только) цитат.
|
||||||
|
|
||||||
|
## Как установить
|
||||||
|
|
||||||
|
Процесс установки несложен, однако требует следования чётким инструкциям, которые приведены ниже. Если что-то пошло не так по причине самовольности юзверя, разработчик не виноват!
|
||||||
|
|
||||||
|
И да, процесс установки описан для линукс энджойеров. Разработчик не гарантирует корректную работу прилажки на ОСях других семейств. Впрочем, никто вам не запрещает пробовать и экспериментировать... Короче, я вас предупредил.
|
||||||
|
|
||||||
|
Перед началом надо скачать данный репозиторий и распаковать. Распаковывать можно куда угодно, но не меняйте структуру без чёткого понимания, что вы и зачем делаете. Я вас снова предупредил.
|
||||||
|
|
||||||
|
Итак, первым делом надо подготовить базу данных, в которой СказаНулл будет хранить цитаты и прочие данные. Вам понадобится PostgreSQL. Установите (если не установлен) и запустите сервер СУБД. Создайте отдельную пустую базу данных, затем подгрузите и выполните скрипт [`database/db-create.sql`](database/db-create.sql). Поздравляю, база данных готова.
|
||||||
|
|
||||||
|
Далее необходимо создать файл конфига. Специально для вас я сделал, так сказать, рыбу данного файла — она находится в файле [`web/web.conf.yml.example`](web/web.conf.yml.example). Но не спешите его менять: лучше его продублировать в `web/web.conf.yml`, и вот уже его можно и нужно изменить в соответствии с вашим сетапом. Параметров там немного, не заблудитесь, да и объяснение к каждому из них приведено. На аглицком.
|
||||||
|
|
||||||
|
Затем убедитесь, что у вас установлен `go` — он необходим для сборки исполняемого бинаря. Если не установлен — установите.
|
||||||
|
|
||||||
|
И наконец, запустите скрипт [`install-web.sh`](install-web.sh). Обязательно под рутом, иначе он (скрипт) вас отругает. Зато если сделаете как сказано, то он вам и конфиг в нужную папку положит, и бинарник соберёт, и даже сервис в systemctl добавит. Красота!
|
||||||
|
|
||||||
|
На этом всё. Теперь на вашей шайтан-машине работает веб-сервис пацанского цитатника СказаНулл.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center"><i>© Masahiko AMANO (H1K0), 2025—present</i></p>
|
||||||
364
database/db-create.sql
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
--
|
||||||
|
-- PostgreSQL database dump
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Dumped from database version 14.15 (Ubuntu 14.15-0ubuntu0.22.04.1)
|
||||||
|
-- Dumped by pg_dump version 15.0
|
||||||
|
|
||||||
|
SET statement_timeout = 0;
|
||||||
|
SET lock_timeout = 0;
|
||||||
|
SET idle_in_transaction_session_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET standard_conforming_strings = on;
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET xmloption = content;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
SET row_security = off;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: public; Type: SCHEMA; Schema: -; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
-- *not* creating schema, since initdb creates it
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_role; Type: TYPE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TYPE public.user_role AS ENUM (
|
||||||
|
'admin',
|
||||||
|
'editor',
|
||||||
|
'viewer'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quote; Type: TYPE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TYPE public.quote AS (
|
||||||
|
id uuid,
|
||||||
|
text text,
|
||||||
|
author character varying(256),
|
||||||
|
datetime timestamp with time zone,
|
||||||
|
creator_id uuid,
|
||||||
|
creator_name character varying(32),
|
||||||
|
creator_login character varying(32),
|
||||||
|
creator_role public.user_role,
|
||||||
|
creator_telegram_id bigint
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user; Type: TYPE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TYPE public."user" AS (
|
||||||
|
id uuid,
|
||||||
|
name character varying(32),
|
||||||
|
login character varying(32),
|
||||||
|
role public.user_role,
|
||||||
|
telegram_id bigint
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quote_add(uuid, text, character varying, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.quote_add(user_id uuid, quote_text text, quote_author character varying DEFAULT NULL::character varying, quote_datetime timestamp with time zone DEFAULT statement_timestamp(), OUT new_quote public.quote) RETURNS public.quote
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
curr_user_role user_role;
|
||||||
|
new_quote_id uuid;
|
||||||
|
BEGIN
|
||||||
|
SELECT "role" FROM users WHERE id=user_id INTO curr_user_role;
|
||||||
|
IF curr_user_role IS NULL THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
IF curr_user_role='viewer' THEN
|
||||||
|
RAISE '403:Увы и ах, такие права у тебя ещё не скачаны :(';
|
||||||
|
END IF;
|
||||||
|
INSERT INTO quotes (text, author, datetime, creator_id)
|
||||||
|
VALUES (quote_text, quote_author, quote_datetime, user_id)
|
||||||
|
RETURNING id INTO new_quote_id;
|
||||||
|
SELECT * FROM quote_get(user_id, new_quote_id) INTO new_quote;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quote_delete(uuid, uuid); Type: PROCEDURE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE PROCEDURE public.quote_delete(IN user_id uuid, IN quote_id uuid)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
quote_temp "quote";
|
||||||
|
curr_user_role user_role;
|
||||||
|
BEGIN
|
||||||
|
SELECT "role" FROM users WHERE id=user_id INTO curr_user_role;
|
||||||
|
IF curr_user_role IS NULL THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
SELECT * FROM quote_get(user_id, quote_id) INTO quote_temp;
|
||||||
|
IF quote_temp IS NULL THEN
|
||||||
|
RAISE '404:Такую цитату ещё не сказанули';
|
||||||
|
END IF;
|
||||||
|
IF curr_user_role='viewer' OR quote_temp.creator_id!=user_id AND curr_user_role!='admin' THEN
|
||||||
|
RAISE '403:Увы и ах, такие права у тебя ещё не скачаны :(';
|
||||||
|
END IF;
|
||||||
|
UPDATE quotes
|
||||||
|
SET is_removed=true
|
||||||
|
WHERE id=quote_id;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quote_get(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.quote_get(user_id uuid, quote_id uuid, OUT ret_quote public.quote) RETURNS public.quote
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
PERFORM 1 FROM users WHERE id=user_id;
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
SELECT * FROM quotes_get(user_id) WHERE id=quote_id INTO ret_quote;
|
||||||
|
if ret_quote IS NULL THEN
|
||||||
|
RAISE '404:Такую цитату ещё не сказанули';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quote_update(uuid, uuid, text, character varying, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.quote_update(user_id uuid, quote_id uuid, new_text text DEFAULT NULL::text, new_author character varying DEFAULT NULL::character varying, new_datetime timestamp with time zone DEFAULT NULL::timestamp with time zone, OUT updated_quote public.quote) RETURNS public.quote
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
quote_temp "quote";
|
||||||
|
curr_user_role user_role;
|
||||||
|
BEGIN
|
||||||
|
SELECT "role" FROM users WHERE id=user_id INTO curr_user_role;
|
||||||
|
IF curr_user_role IS NULL THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
SELECT * FROM quote_get(user_id, quote_id) INTO quote_temp;
|
||||||
|
IF quote_temp IS NULL THEN
|
||||||
|
RAISE '404:Такую цитату ещё не сказанули';
|
||||||
|
END IF;
|
||||||
|
IF curr_user_role='viewer' OR quote_temp.creator_id!=user_id AND curr_user_role!='admin' THEN
|
||||||
|
RAISE '403:Увы и ах, такие права у тебя ещё не скачаны :(';
|
||||||
|
END IF;
|
||||||
|
UPDATE quotes
|
||||||
|
SET
|
||||||
|
text=coalesce(new_text, text),
|
||||||
|
author=coalesce(new_author, author),
|
||||||
|
datetime=coalesce(new_datetime, datetime)
|
||||||
|
WHERE id=quote_id;
|
||||||
|
SELECT * FROM quote_get(user_id, quote_id) INTO updated_quote;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quotes_get(uuid); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.quotes_get(user_id uuid) RETURNS SETOF public.quote
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
PERFORM 1 FROM users WHERE id=user_id;
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
RETURN QUERY SELECT q.id, q.text, q.author, q.datetime, u.id, u.name, u.login, u.role, u.telegram_id
|
||||||
|
FROM quotes q
|
||||||
|
JOIN users u ON u.id=q.creator_id
|
||||||
|
WHERE NOT q.is_removed;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_auth(character varying, character varying); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.user_auth(user_login character varying, user_password character varying, OUT ret_user public."user") RETURNS public."user"
|
||||||
|
LANGUAGE plpgsql STABLE PARALLEL SAFE
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
SELECT u.id, u.name, u.login, u.role, u.telegram_id
|
||||||
|
FROM users u
|
||||||
|
WHERE u.login=user_login AND u.password=crypt(user_password, u.password)
|
||||||
|
INTO ret_user;
|
||||||
|
IF ret_user IS NULL THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_get(uuid); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.user_get(user_id uuid, OUT ret_user public."user") RETURNS public."user"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
SELECT id, name, login, role, telegram_id FROM users WHERE id=user_id INTO ret_user;
|
||||||
|
IF ret_user IS NULL THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_update(uuid, character varying, character varying, bigint, character varying); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.user_update(user_id uuid, new_name character varying DEFAULT NULL::character varying, new_login character varying DEFAULT NULL::character varying, new_telegram_id bigint DEFAULT NULL::bigint, new_password character varying DEFAULT NULL::character varying, OUT updated_user public."user") RETURNS public."user"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
PERFORM 1 FROM users WHERE id=user_id;
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE '401:Чел, ты кто? Я тебя не знаю';
|
||||||
|
END IF;
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
name=coalesce(new_name, name),
|
||||||
|
login=coalesce(new_login, login),
|
||||||
|
telegram_id=coalesce(new_telegram_id, telegram_id),
|
||||||
|
password=coalesce(crypt(new_password, gen_salt('bf')), password)
|
||||||
|
WHERE id=user_id;
|
||||||
|
SELECT * FROM user_get(user_id) WHERE id=user_id INTO updated_user;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
SET default_table_access_method = heap;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quotes; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.quotes (
|
||||||
|
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
|
||||||
|
text text NOT NULL,
|
||||||
|
datetime timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
author character varying(256) NOT NULL,
|
||||||
|
creator_id uuid NOT NULL,
|
||||||
|
is_removed boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.users (
|
||||||
|
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
|
||||||
|
name character varying(32) NOT NULL,
|
||||||
|
telegram_id bigint NOT NULL,
|
||||||
|
is_editor boolean DEFAULT false NOT NULL,
|
||||||
|
login character varying(32) NOT NULL,
|
||||||
|
password text NOT NULL,
|
||||||
|
role public.user_role DEFAULT 'viewer'::public.user_role NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quotes prm__quotes; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.quotes
|
||||||
|
ADD CONSTRAINT prm__quotes PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users prm__users; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.users
|
||||||
|
ADD CONSTRAINT prm__users PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users uni__users__login; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.users
|
||||||
|
ADD CONSTRAINT uni__users__login UNIQUE (login);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users uni__users__name; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.users
|
||||||
|
ADD CONSTRAINT uni__users__name UNIQUE (name);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users uni__users__telegram_uid; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.users
|
||||||
|
ADD CONSTRAINT uni__users__telegram_uid UNIQUE (telegram_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: idx__quotes__creator_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX idx__quotes__creator_id ON public.quotes USING hash (creator_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: idx__quotes__date; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX idx__quotes__date ON public.quotes USING btree (datetime DESC NULLS LAST);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: quotes frn__quotes__creator_id; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.quotes
|
||||||
|
ADD CONSTRAINT frn__quotes__creator_id FOREIGN KEY (creator_id) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- PostgreSQL database dump complete
|
||||||
|
--
|
||||||
|
|
||||||
30
install-web.sh
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "Ты ридмишку не читал что ли? Сказано же русским языком: \"ПОД РУТОМ запускать\"!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||||
|
|
||||||
|
echo "Идёт установка..." &&
|
||||||
|
|
||||||
|
echo "Чудодейство конфигурации..." &&
|
||||||
|
mkdir -p /etc/skazanull &&
|
||||||
|
cp $SCRIPT_DIR/web/web.conf.yml /etc/skazanull/web.conf.yml &&
|
||||||
|
chmod 640 /etc/skazanull/web.conf.yml &&
|
||||||
|
chown www-data:www-data /etc/skazanull/web.conf.yml &&
|
||||||
|
|
||||||
|
echo "Сборка..." &&
|
||||||
|
go build -C $SCRIPT_DIR/web -o $SCRIPT_DIR/bin/skazanull $SCRIPT_DIR/web/cmd/main.go &&
|
||||||
|
mkdir -p /opt/skazanull/bin &&
|
||||||
|
cp $SCRIPT_DIR/bin/skazanull /opt/skazanull/bin/skazanull &&
|
||||||
|
chmod 755 /opt/skazanull/bin/skazanull &&
|
||||||
|
|
||||||
|
echo "Установка сервиса в systemctl..." &&
|
||||||
|
cp $SCRIPT_DIR/web/snw.service /etc/systemd/system &&
|
||||||
|
chmod 644 /etc/systemd/system/snw.service &&
|
||||||
|
systemctl daemon-reload &&
|
||||||
|
systemctl start snw &&
|
||||||
|
|
||||||
|
echo "СказаНулл успешно установлен."
|
||||||
18
web/cmd/main.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/H1K0/SkazaNull/conf"
|
||||||
|
"github.com/H1K0/SkazaNull/db"
|
||||||
|
"github.com/H1K0/SkazaNull/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config, err := conf.GetConf()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error while loading configuration file: %s\n", err)
|
||||||
|
}
|
||||||
|
db.InitDB(config.DBConfig)
|
||||||
|
server.Serve(config.Interface, []byte(config.EncryptionKey))
|
||||||
|
}
|
||||||
24
web/conf/conf.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CONF_FILE = "/etc/skazanull/web.conf.yml"
|
||||||
|
|
||||||
|
type Conf struct {
|
||||||
|
DBConfig string `yaml:"db_config"`
|
||||||
|
Interface string `yaml:"interface"`
|
||||||
|
EncryptionKey string `yaml:"encryption_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConf() (conf Conf, err error) {
|
||||||
|
confFile, err := os.ReadFile(CONF_FILE)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(confFile, &conf)
|
||||||
|
return
|
||||||
|
}
|
||||||
9
web/embed/embed.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package embed
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed templates
|
||||||
|
var TemplatesFS embed.FS
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var StaticFS embed.FS
|
||||||
64
web/embed/static/css/google-fonts.css
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgFE_.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/static/webfonts/nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 972 KiB After Width: | Height: | Size: 972 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@ -18,7 +18,7 @@
|
|||||||
<meta name="msapplication-TileColor" content="#8B6D5C">
|
<meta name="msapplication-TileColor" content="#8B6D5C">
|
||||||
<meta name="msapplication-TileImage" content="/static/images/ms-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="/static/images/ms-icon-144x144.png">
|
||||||
<meta name="theme-color" content="#8B6D5C">
|
<meta name="theme-color" content="#8B6D5C">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
|
<link href="/static/css/google-fonts.css" rel="stylesheet" />
|
||||||
<link href="/static/css/font-awesome.6.4.0.min.css" rel="stylesheet"/>
|
<link href="/static/css/font-awesome.6.4.0.min.css" rel="stylesheet"/>
|
||||||
<link href="/static/css/tailwind-custom.css" rel="stylesheet"/>
|
<link href="/static/css/tailwind-custom.css" rel="stylesheet"/>
|
||||||
<link href="/static/css/skazanull.css" rel="stylesheet"/>
|
<link href="/static/css/skazanull.css" rel="stylesheet"/>
|
||||||
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/gin-contrib/sessions v1.0.2
|
github.com/gin-contrib/sessions v1.0.2
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/jackc/pgx/v5 v5.7.2
|
github.com/jackc/pgx/v5 v5.7.2
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -46,5 +47,4 @@ require (
|
|||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,27 +1,40 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/H1K0/SkazaNull/api"
|
"github.com/H1K0/SkazaNull/api"
|
||||||
|
"github.com/H1K0/SkazaNull/embed"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Serve(addr string) {
|
func Serve(addr string, encryptionKey []byte) {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
store := cookie.NewStore([]byte("secret"))
|
store := cookie.NewStore(encryptionKey)
|
||||||
store.Options(sessions.Options{Path: "/"})
|
store.Options(sessions.Options{Path: "/"})
|
||||||
r.Use(sessions.Sessions("session", store))
|
r.Use(sessions.Sessions("session", store))
|
||||||
|
|
||||||
|
tmpl := template.Must(template.ParseFS(embed.TemplatesFS, "templates/*.html"))
|
||||||
|
r.SetHTMLTemplate(tmpl)
|
||||||
|
|
||||||
api.RegisterRoutes(r)
|
api.RegisterRoutes(r)
|
||||||
|
|
||||||
r.LoadHTMLGlob("templates/*.html")
|
static, err := fs.Sub(embed.StaticFS, "static")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get subs of embedded static FS: %s\n", err)
|
||||||
|
}
|
||||||
|
r.StaticFS("/static/", http.FS(static))
|
||||||
|
r.StaticFileFS("/favicon.ico", "static/service/favicon.ico", http.FS(embed.StaticFS))
|
||||||
|
r.StaticFileFS("/skazanull.webmanifest", "static/service/skazanull.webmanifest", http.FS(embed.StaticFS))
|
||||||
|
r.StaticFileFS("/browserconfig.xml", "static/service/browserconfig.xml", http.FS(embed.StaticFS))
|
||||||
|
|
||||||
r.Static("/favicon.ico", "./static/service/favicon.ico")
|
|
||||||
r.Static("/skazanull.webmanifest", "./static/service/skazanull.webmanifest")
|
|
||||||
r.Static("/browserconfig.xml", "./static/service/browserconfig.xml")
|
|
||||||
r.Static("/static", "./static")
|
|
||||||
r.GET("/", api.MiddlewareAuth, root)
|
r.GET("/", api.MiddlewareAuth, root)
|
||||||
r.GET("/quotes", api.MiddlewareAuth, middlewareAuth, quotes)
|
r.GET("/quotes", api.MiddlewareAuth, middlewareAuth, quotes)
|
||||||
r.GET("/settings", api.MiddlewareAuth, middlewareAuth, settings)
|
r.GET("/settings", api.MiddlewareAuth, middlewareAuth, settings)
|
||||||
|
|||||||
11
web/snw.service
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=SkazaNull Web
|
||||||
|
After=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/opt/skazanull/bin/skazanull
|
||||||
|
User=www-data
|
||||||
|
TimeoutStopSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
10
web/web.conf.yml.example
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Database configuration string
|
||||||
|
db_config: postgres://user:password@host:port/db?application_name=SkazaNull%20Web
|
||||||
|
|
||||||
|
# The interface on which to run the webserver
|
||||||
|
interface: ":9672"
|
||||||
|
|
||||||
|
# Encryption key for sessions.
|
||||||
|
# Change it to a string of exactly 16, 32 or 64 chars
|
||||||
|
# Avoid the "#" character in your APP_KEY, it may break things.
|
||||||
|
encryption_key: some_secret_key
|
||||||