feat: automatic db creation and migration
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
- make sure that you have `pdo_sqlite`
|
- make sure that you have `pdo_sqlite`
|
||||||
- oonix `chmod +x ./dev.sh` and then `./dev.sh`
|
- oonix `chmod +x ./dev.sh` and then `./dev.sh`
|
||||||
- uindos `cat ./dev.sh | iex` in poersel
|
- uindos `cat ./dev.sh | iex` in poersel
|
||||||
|
- for production generate dist with `./bundle.sh`
|
||||||
|
|
||||||
### IMPORTANT
|
### IMPORTANT
|
||||||
|
|
||||||
@@ -14,5 +15,7 @@
|
|||||||
- dx4dashboard
|
- dx4dashboard
|
||||||
- change `base` in `vite.config.ts`
|
- change `base` in `vite.config.ts`
|
||||||
- change `API_BASE_URL` and `CLIENT_FRONTEND_URL` in `src/App.jsx`
|
- change `API_BASE_URL` and `CLIENT_FRONTEND_URL` in `src/App.jsx`
|
||||||
- backend
|
- **backend !!**
|
||||||
- change `ADMIN_PASS` and `API_PATH` in `admin_api.php`
|
- change `ADMIN_PASS` and `API_PATH` in `admin_api.php`
|
||||||
|
- change `$password` in `adm/phpliteadmin.config.php`
|
||||||
|
- **block http requests to `data.db`!!!**
|
||||||
@@ -2,3 +2,5 @@ api.lock
|
|||||||
_api.lock
|
_api.lock
|
||||||
|
|
||||||
data.db
|
data.db
|
||||||
|
dist
|
||||||
|
k-api.updates.php
|
||||||
@@ -13,7 +13,7 @@ function db()
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function relativePathToUrl($rel)
|
function relativePathToUrl(string $rel)
|
||||||
{
|
{
|
||||||
$parts = explode('/', dirname($_SERVER['SCRIPT_NAME']) . '/' . $rel);
|
$parts = explode('/', dirname($_SERVER['SCRIPT_NAME']) . '/' . $rel);
|
||||||
$norm = [];
|
$norm = [];
|
||||||
@@ -37,7 +37,7 @@ function FastDBReq(PDO $db, string $sql, array $params = []): array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Login($req)
|
function Login(mixed $req)
|
||||||
{
|
{
|
||||||
return isset($req->data['pass']) && $req->data['pass'] == ADMIN_PASS;
|
return isset($req->data['pass']) && $req->data['pass'] == ADMIN_PASS;
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ CreateApi(function ($api) {
|
|||||||
$api->on('/', 'GET', function ($req) {
|
$api->on('/', 'GET', function ($req) {
|
||||||
return 'hi';
|
return 'hi';
|
||||||
});
|
});
|
||||||
$api->on('/phpliteadmin', 'GET', function ($req) {
|
$api->on('/phpliteadmin', 'GET', function () {
|
||||||
header('Location: ./adm/phpliteadmin.php');
|
header('Location: ./adm/phpliteadmin.php');
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ CreateApi(function ($api) {
|
|||||||
die($cnt);
|
die($cnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$api->useUpdates(db());
|
||||||
|
|
||||||
$api->on('/', 'GET', function () {
|
$api->on('/', 'GET', function () {
|
||||||
return 'dengi';
|
return 'dengi';
|
||||||
});
|
});
|
||||||
|
|||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
set -e
|
||||||
|
# copy only nessesary files
|
||||||
|
|
||||||
|
echo "Copying files..."
|
||||||
|
mkdir -p dist
|
||||||
|
cp -r *.php sql-updates/ adm/ dist/
|
||||||
|
|
||||||
|
set +e
|
||||||
|
echo "Downloading phpLiteAdmin"
|
||||||
|
wget -nc https://bitbucket.org/phpliteadmin/public/downloads/phpLiteAdmin_v1-9-8-2.zip
|
||||||
|
unzip phpLiteAdmin_v1-9-8-2.zip phpliteadmin.php
|
||||||
|
mv phpliteadmin.php dist/adm
|
||||||
|
|
||||||
|
echo "Cleaning up"
|
||||||
|
|
||||||
|
rm -f phpLiteAdmin_v1-9-8-2.zip
|
||||||
|
set -e
|
||||||
|
rm -f dist/k-api.updates-status.php
|
||||||
|
|
||||||
|
echo "
|
||||||
|
|
||||||
|
|
||||||
|
Done."
|
||||||
+31
-1
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
const UPDATES_STATUS_FILE = "k-api.updates-status.php";
|
||||||
|
const SQL_UPDATES_FOLDER = "./sql-updates";
|
||||||
|
|
||||||
/** @param callable(_K_Api): void $apiFun */
|
/** @param callable(_K_Api): void $apiFun */
|
||||||
function CreateApi(callable $apiFun, bool $devmode = false)
|
function CreateApi(callable $apiFun, bool $devmode = false)
|
||||||
@@ -66,13 +68,41 @@ class _K_Api
|
|||||||
$_a = "unknown return type ($_type), (available bool, array, numeric, string)";
|
$_a = "unknown return type ($_type), (available bool, array, numeric, string)";
|
||||||
}
|
}
|
||||||
die("ERROR: " . $_a);
|
die("ERROR: " . $_a);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function useUpdates(PDO $db)
|
||||||
|
{
|
||||||
|
$currentVersion = file_get_contents(UPDATES_STATUS_FILE);
|
||||||
|
if ($currentVersion === false) {
|
||||||
|
file_put_contents(UPDATES_STATUS_FILE, "<?php // -1");
|
||||||
|
$currentVersion = -1;
|
||||||
|
} else {
|
||||||
|
$currentVersion = intval(substr($currentVersion, 9));
|
||||||
|
}
|
||||||
|
$files = glob(SQL_UPDATES_FOLDER . '/*.sql');
|
||||||
|
$updateVersion = null;
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (!is_file($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$version = intval(basename($file, '.sql'));
|
||||||
|
if ($version > $currentVersion) {
|
||||||
|
$db->exec(file_get_contents($file));
|
||||||
|
$updateVersion = $version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($updateVersion !== null) {
|
||||||
|
file_put_contents(
|
||||||
|
UPDATES_STATUS_FILE,
|
||||||
|
"<?php // " . $updateVersion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function end()
|
public function end()
|
||||||
{
|
{
|
||||||
if (!$this->has_exec) {
|
if (!$this->has_exec) {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<?php // 0
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/* initial sql database (dxv4 2.1) */
|
||||||
|
|
||||||
|
CREATE TABLE admins (
|
||||||
|
adminId TEXT UNIQUE
|
||||||
|
NOT NULL,
|
||||||
|
adminName TEXT NOT NULL,
|
||||||
|
adminClass TEXT NOT NULL,
|
||||||
|
adminPrems INTEGER DEFAULT (666)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE changes (
|
||||||
|
classId TEXT,
|
||||||
|
forDay TEXT,
|
||||||
|
content TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE classes (
|
||||||
|
classid TEXT UNIQUE
|
||||||
|
NOT NULL,
|
||||||
|
userclassname TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE rings (
|
||||||
|
classId TEXT,
|
||||||
|
fromDate TEXT,
|
||||||
|
content TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE schedules (
|
||||||
|
classId TEXT,
|
||||||
|
fromDate TEXT,
|
||||||
|
dayOfWeek INTEGER,
|
||||||
|
content TEXT
|
||||||
|
);
|
||||||
@@ -48,7 +48,6 @@ export default function DashPage({
|
|||||||
|
|
||||||
const tabs: Record<string, string> = {
|
const tabs: Record<string, string> = {
|
||||||
class: "классы",
|
class: "классы",
|
||||||
admin: "админы",
|
|
||||||
server: "сервер",
|
server: "сервер",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,8 +84,6 @@ export default function DashPage({
|
|||||||
setRoute={(p: string) => setsetRoute(setRoute, p)}
|
setRoute={(p: string) => setsetRoute(setRoute, p)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "admin":
|
|
||||||
return <AdminsMainEditor pass={pass as string} />;
|
|
||||||
case "server":
|
case "server":
|
||||||
return <ServerEditor pass={pass as string} />;
|
return <ServerEditor pass={pass as string} />;
|
||||||
}
|
}
|
||||||
@@ -183,17 +180,10 @@ export const CopyClassLink = ({
|
|||||||
</DengiButton>
|
</DengiButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
function AdminsMainEditor({ pass }: { pass: string }) {
|
|
||||||
return (
|
|
||||||
<div className="block">
|
|
||||||
<img src="https://od.lk/s/MTBfMzQ0MTQ0Njg5Xw/admin1.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()} />
|
|
||||||
{/* <img src="https://od.lk/s/MTBfMzQ0MTQ0NjkyXw/admin2.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()}/> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function ServerEditor({ pass }: { pass: string }) {
|
function ServerEditor({ pass }: { pass: string }) {
|
||||||
const [serverData, setServerData] = useState<Record<string, string> | false>(
|
const [serverData, setServerData] = useState<Record<string, string> | false>(
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
const [idle, setIdle] = useState(false);
|
const [idle, setIdle] = useState(false);
|
||||||
const [showL, setShowL] = useState(false);
|
const [showL, setShowL] = useState(false);
|
||||||
@@ -390,107 +380,6 @@ function CreateClass({ pass, update }: { pass: string; update: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function AdminProfileEditor({
|
|
||||||
// adminFrId,
|
|
||||||
// setAdminFrId,
|
|
||||||
// adminId,
|
|
||||||
// classId,
|
|
||||||
// pickmeMode = false,
|
|
||||||
// }) {
|
|
||||||
// return (
|
|
||||||
// <div className=" bg-grain bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%]">
|
|
||||||
// <table className="">
|
|
||||||
// <tbody className="*:*:w-10 *:*:h-10">
|
|
||||||
// <tr>
|
|
||||||
// <td className="">имя</td>
|
|
||||||
// <td className="w-50!">
|
|
||||||
// <DengiEditable
|
|
||||||
// value={adminFrId}
|
|
||||||
// pink={pickmeMode}
|
|
||||||
// onSubmit={async (e) => {
|
|
||||||
// e.setIdle(true);
|
|
||||||
// const r = await AdminUpdateRequest(API_BASE_URL, adminId, {
|
|
||||||
// action: "change_name",
|
|
||||||
// value: e.value.trim(),
|
|
||||||
// });
|
|
||||||
// if (r[1] == true) setAdminFrId(e.value);
|
|
||||||
// e.setIdle(false);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// <tr>
|
|
||||||
// <td>id</td>
|
|
||||||
// <td>
|
|
||||||
// <DengiEditable
|
|
||||||
// value={adminId}
|
|
||||||
// password={true}
|
|
||||||
// pink={pickmeMode}
|
|
||||||
// disabled
|
|
||||||
// />
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// <tr>
|
|
||||||
// <td>class</td>
|
|
||||||
// <td className="font-mono text-center select-all selectable">
|
|
||||||
// {classId ? classId : <TextPreloader />}
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// </tbody>
|
|
||||||
// </table>
|
|
||||||
// <CopyLink {...{ adminId, classId }} />
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const CopyLink = ({ classId, adminId }) => {
|
|
||||||
// const [showDiag, setShowDiag] = useState(false);
|
|
||||||
// const [seenDiag, setSeenDiag] = useState(false);
|
|
||||||
// const [linkCopied, setLinkCopied] = useState(false);
|
|
||||||
// const copy = () => {
|
|
||||||
// copyDXLink(classId, adminId).then((a) => {
|
|
||||||
// if (a) {
|
|
||||||
// setLinkCopied(true);
|
|
||||||
// setTimeout(() => {
|
|
||||||
// setLinkCopied(false);
|
|
||||||
// }, 2000);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// return (
|
|
||||||
// <div className="flex justify-end mt-2">
|
|
||||||
// <DengiYesOrNoDialog
|
|
||||||
// show={showDiag}
|
|
||||||
// content={{
|
|
||||||
// message: (
|
|
||||||
// <p>
|
|
||||||
// <b>любой</b>, кто получит ссылку,{" "}
|
|
||||||
// <b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
|
|
||||||
// </p>
|
|
||||||
// ),
|
|
||||||
// yesBtn: "да",
|
|
||||||
// noBtn: "нет",
|
|
||||||
// yesIcon: <BiCopy />,
|
|
||||||
// noIcon: <BiX size={20} />,
|
|
||||||
// bgDissmis: true,
|
|
||||||
// yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
|
|
||||||
// }}
|
|
||||||
// onAnswer={(answ) => {
|
|
||||||
// if (answ) {
|
|
||||||
// setSeenDiag(true);
|
|
||||||
// copy();
|
|
||||||
// }
|
|
||||||
// setShowDiag(false);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// <DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
|
|
||||||
// <BiLink /> <BiError color={"orange"} />
|
|
||||||
// <span>{linkCopied ? "скопировано" : "коп ссылку на админку"}</span>
|
|
||||||
// </DengiButton>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const CoolBg = ({}) => {
|
export const CoolBg = ({}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user