feat: automatic db creation and migration

This commit is contained in:
2026-08-28 16:32:25 +03:00
parent 114ba523f4
commit 4fbb8a8e2e
9 changed files with 104 additions and 120 deletions
+5 -2
View File
@@ -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`!!!**
+3 -1
View File
@@ -1,4 +1,6 @@
api.lock api.lock
_api.lock _api.lock
data.db data.db
dist
k-api.updates.php
+3 -3
View File
@@ -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 "";
}); });
+2
View File
@@ -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';
}); });
+23
View File
@@ -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
View File
@@ -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) {
+1
View File
@@ -0,0 +1 @@
<?php // 0
+34
View File
@@ -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
);
+2 -113
View File
@@ -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 (
<> <>