db integration and scripts
This commit is contained in:
parent
4577bb72b9
commit
31ce135b3a
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
config.json
|
||||
dist/
|
||||
dist/
|
||||
*.sqlite
|
@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"tailwind": "npx tailwindcss -i ./src/client/public/css/main.css -o ./src/client/public/css/tailwind.css",
|
||||
"build": "./build.sh",
|
||||
"build": "./scripts/build.sh",
|
||||
"dev": "nodemon -r tsconfig-paths/register ./src/app.ts",
|
||||
"start": "node dist/app.js"
|
||||
},
|
||||
@ -28,10 +28,12 @@
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"jquery": "^3.7.1",
|
||||
"knex": "^3.1.0",
|
||||
"ncp": "^2.0.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-discord": "^0.1.4",
|
||||
"preline": "^2.7.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,5 +1,7 @@
|
||||
cd "$(dirname "$0")/../"
|
||||
|
||||
echo "Erasing previous dist folder"
|
||||
rm -r ./dist
|
||||
rm -rf ./dist
|
||||
|
||||
echo "Compiling tailwind css ..."
|
||||
npx tailwindcss -i ./src/client/public/css/main.css -o ./src/client/public/css/tailwind.css
|
||||
@ -7,6 +9,9 @@ npx tailwindcss -i ./src/client/public/css/main.css -o ./src/client/public/css/t
|
||||
echo "Compiling typescript ..."
|
||||
npx tsc --project ./tsconfig.json
|
||||
|
||||
echo "Copying client files"
|
||||
cp -r src/client dist/client
|
||||
|
||||
echo "Building typescript path aliases ..."
|
||||
npx tsc-alias -p ./tsconfig.json
|
||||
|
2
scripts/migrate.sh
Executable file
2
scripts/migrate.sh
Executable file
@ -0,0 +1,2 @@
|
||||
cd "$(dirname "$0")/../"
|
||||
npx knex migrate:latest --knexfile ./src/knexfile.ts
|
2
scripts/seeds.sh
Executable file
2
scripts/seeds.sh
Executable file
@ -0,0 +1,2 @@
|
||||
cd "$(dirname "$0")/../"
|
||||
npx knex seed:run --knexfile ./src/knexfile.ts
|
26
src/app.ts
26
src/app.ts
@ -1,3 +1,4 @@
|
||||
import path from "path";
|
||||
import express from "express";
|
||||
import engine from "ejs-mate";
|
||||
import passport from "passport";
|
||||
@ -7,28 +8,29 @@ import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import "@bot/bot";
|
||||
import { setupPassport } from "@server/controllers/auth";
|
||||
import { setupPassport } from "@server/controllers/auth.web.controller";
|
||||
import { attachUser } from "@server/middleware/attachUser";
|
||||
import { flashMiddleware } from "@server/middleware/flash";
|
||||
import { ensureAuthenticated } from "@server/middleware/authenticated";
|
||||
|
||||
// import routers & middleware
|
||||
import { attachGuilds } from "@server/middleware/attachGuilds";
|
||||
import { router as homeRouter } from "@server/routes/home";
|
||||
import { router as guildRouter } from "@server/routes/guild";
|
||||
import { router as authRouter } from "@server/routes/auth";
|
||||
import homeWebRouter from "@server/routes/home.web.routes";
|
||||
import guildApiRouter from "@server/routes/guild.api.routes";
|
||||
import guildWebRouter from "@server/routes/guild.web.routes";
|
||||
import authWebRouter from "@server/routes/auth.web.routes";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.engine("ejs", engine);
|
||||
app.set("views", "./src/client/views");
|
||||
app.set("views", path.resolve(__dirname, "client/views"));
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
// Public files, including 3rd party resources (foreign)
|
||||
app.use("/static", express.static("./src/client/public"));
|
||||
app.use("/static/foreign/preline.js", express.static("./node_modules/preline/dist/preline.js"));
|
||||
app.use("/static/foreign/jquery.js", express.static("./node_modules/jquery/dist/jquery.min.js"));
|
||||
app.use("/static/foreign/dataTables.js", express.static("./node_modules/datatables.net-dt/js/dataTables.dataTables.min.js"));
|
||||
app.use("/static", express.static(path.resolve(__dirname, "client/public")));
|
||||
app.use("/static/foreign/preline.js", express.static(path.resolve(__dirname, "../node_modules/preline/dist/preline.js")));
|
||||
app.use("/static/foreign/jquery.js", express.static(path.resolve(__dirname, "../node_modules/jquery/dist/jquery.min.js")));
|
||||
app.use("/static/foreign/dataTables.js", express.static(path.resolve(__dirname, "../node_modules/datatables.net/js/dataTables.min.js")));
|
||||
|
||||
// User authentication & sessions
|
||||
app.use(session({
|
||||
@ -45,9 +47,9 @@ app.use(flash());
|
||||
app.use(flashMiddleware);
|
||||
|
||||
// register routers & middleware
|
||||
app.use("/auth", authRouter);
|
||||
app.use("/guild", ensureAuthenticated, attachUser, attachGuilds, guildRouter);
|
||||
app.use("/", ensureAuthenticated, attachUser, attachGuilds, homeRouter);
|
||||
app.use("/auth", authWebRouter);
|
||||
app.use("/guild", ensureAuthenticated, attachUser, attachGuilds, guildWebRouter, guildApiRouter);
|
||||
app.use("/", ensureAuthenticated, attachUser, attachGuilds, homeWebRouter);
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
|
@ -14,7 +14,7 @@ client.on("ready", () => {
|
||||
throw Error("Client is null");
|
||||
}
|
||||
|
||||
client.user.setActivity("Set Activity", { type: ActivityType.Watching });
|
||||
client.user.setActivity("new sources", { type: ActivityType.Watching });
|
||||
console.log(`Discord Bot '${client.user.displayName}' is online!`)
|
||||
});
|
||||
|
||||
|
@ -918,6 +918,10 @@ select {
|
||||
margin-inline-start: auto !important;
|
||||
}
|
||||
|
||||
.-me-0\.5 {
|
||||
margin-inline-end: -0.125rem;
|
||||
}
|
||||
|
||||
.-mt-px {
|
||||
margin-top: -1px;
|
||||
}
|
||||
@ -1151,10 +1155,6 @@ select {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
.w-\[100rem\] {
|
||||
width: 100rem;
|
||||
}
|
||||
|
||||
.w-\[260px\] {
|
||||
width: 260px;
|
||||
}
|
||||
@ -1543,6 +1543,11 @@ select {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.bg-yellow-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@ -1872,6 +1877,11 @@ select {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.text-yellow-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(133 77 14 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
@ -2562,6 +2572,26 @@ hs-accordion-toggle w-full text-start flex items-center gap-x-3.5 py-2 px-2.5 te
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dt-ordering-asc .hs-datatable-ordering-asc\:text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-asc.hs-datatable-ordering-asc\:text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-desc .hs-datatable-ordering-desc\:text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-desc.hs-datatable-ordering-desc\:text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.complete .hs-file-upload-complete\:bg-green-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
|
||||
@ -2830,6 +2860,10 @@ hs-accordion-toggle w-full text-start flex items-center gap-x-3.5 py-2 px-2.5 te
|
||||
background-color: rgb(20 184 166 / 0.1);
|
||||
}
|
||||
|
||||
.dark\:bg-yellow-500\/10:where(.dark, .dark *) {
|
||||
background-color: rgb(234 179 8 / 0.1);
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:where(.dark, .dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@ -2900,6 +2934,11 @@ hs-accordion-toggle w-full text-start flex items-center gap-x-3.5 py-2 px-2.5 te
|
||||
color: rgb(255 255 255 / 0.6);
|
||||
}
|
||||
|
||||
.dark\:text-yellow-500:where(.dark, .dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(234 179 8 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dark\:placeholder-neutral-500:where(.dark, .dark *)::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(115 115 115 / var(--tw-placeholder-opacity, 1));
|
||||
@ -2994,6 +3033,26 @@ hs-accordion-toggle w-full text-start flex items-center gap-x-3.5 py-2 px-2.5 te
|
||||
--tw-ring-offset-color: #1f2937;
|
||||
}
|
||||
|
||||
.dt-ordering-asc .dark\:hs-datatable-ordering-asc\:text-blue-500:where(.dark, .dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-asc.dark\:hs-datatable-ordering-asc\:text-blue-500:where(.dark, .dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-desc .dark\:hs-datatable-ordering-desc\:text-blue-500:where(.dark, .dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.dt-ordering-desc.dark\:hs-datatable-ordering-desc\:text-blue-500:where(.dark, .dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.\[\&\:\:-webkit-scrollbar-thumb\]\:rounded-full::-webkit-scrollbar-thumb {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
18
src/client/public/js/guild/subscriptions.js
Normal file
18
src/client/public/js/guild/subscriptions.js
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
// Filter by status
|
||||
(function () {
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"][name="filter"]');
|
||||
const { dataTable } = new HSDataTable('#table');
|
||||
|
||||
dataTable.search.fixed('status', function (searchStr, data, index) {
|
||||
const status = data[2].trim().toLowerCase(); // Adjust index based on your dataset
|
||||
|
||||
if (radioButtons[0].checked && status === 'active') return true;
|
||||
if (radioButtons[1].checked && status === 'inactive') return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', () => dataTable.draw());
|
||||
});
|
||||
})();
|
@ -1,5 +1,5 @@
|
||||
<nav class="bg-white dark:bg-neutral-900 border-b dark:border-none">
|
||||
<div class="max-w-[100rem] w-full mx-auto sm:flex sm:flex-row sm:justify-between sm:items-center sm:gap-x-3 py-3 sm:py-5 px-4 sm:px-6 lg:px-8">
|
||||
<div class="w-full mx-auto sm:flex sm:flex-row sm:justify-between sm:items-center sm:gap-x-3 py-3 sm:py-5 px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center gap-x-3">
|
||||
<div class="grow flex items-center gap-x-4">
|
||||
<% if (guild.icon) { %>
|
||||
|
@ -3,14 +3,19 @@
|
||||
<%- include("guildHeader") -%>
|
||||
|
||||
<!-- Table Section -->
|
||||
<div class="max-w-full w-[100rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto overflow-hidden">
|
||||
<div id="table" class="max-w-full overflow-hidden p-4 sm:p-6"> <!-- px-4 py-10 sm:px-6 lg:px-8 lg:py-14 -->
|
||||
<!-- Card -->
|
||||
<div class="flex flex-col">
|
||||
<div class="-m-1.5 "> <!-- overflow-x-auto -->
|
||||
<div class="flex flex-col" data-hs-datatable='{
|
||||
"selecting": true,
|
||||
"rowSelectingOptions": {
|
||||
"selectAllSelector": "#selectAllBox"
|
||||
}
|
||||
}'>
|
||||
<div class="-m-1.5">
|
||||
<div class="max-w-full p-1.5 min-w-full inline-block align-middle">
|
||||
<div class="bg-white border border-gray-200 rounded-md shadow-sm overflow-hidden dark:bg-neutral-900 dark:border-neutral-700">
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-4 gap-3 flex flex-nowrap justify-between items-center border-b border-gray-200 dark:border-neutral-700">
|
||||
<div class="px-6 py-4 gap-3 flex flex-nowrap justify-between items-center border-gray-200 dark:border-neutral-700">
|
||||
<!-- Input -->
|
||||
<div class="hidden sm:block sm:col-span-1">
|
||||
<label for="hs-as-table-product-review-search" class="sr-only">Search</label>
|
||||
@ -78,66 +83,94 @@
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
|
||||
<thead class="bg-gray-50 dark:bg-neutral-800">
|
||||
<tr>
|
||||
<th scope="col" class="ps-6 py-3 text-start">
|
||||
<th scope="col" class="ps-6 py-3 text-start --exclude-from-ordering">
|
||||
<label for="hs-at-with-checkboxes-main" class="flex ml-[1px]">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-main">
|
||||
<input type="checkbox" id="selectAllBox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800">
|
||||
<span class="sr-only">Checkbox</span>
|
||||
</label>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
Name
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
URL
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
Channels
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
Filters
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
Style
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200 text-nowrap">
|
||||
Created at
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
|
||||
<th scope="col" class="px-6 py-3 text-start">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="flex justify-between items-center gap-x-2 cursor-pointer">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-gray-800 dark:text-neutral-200">
|
||||
Status
|
||||
</span>
|
||||
<svg class="size-3.5 ms-1 -me-0.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path class="hs-datatable-ordering-desc:text-blue-600 dark:hs-datatable-ordering-desc:text-blue-500" d="m7 15 5 5 5-5"></path>
|
||||
<path class="hs-datatable-ordering-asc:text-blue-600 dark:hs-datatable-ordering-asc:text-blue-500" d="m7 9 5-5 5 5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@ -151,7 +184,7 @@
|
||||
<td class="size-px whitespace-nowrap">
|
||||
<div class="ps-6 py-3">
|
||||
<label for="hs-at-with-checkboxes-1" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-1">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-1" data-hs-datatable-row-selecting-individual="">
|
||||
<span class="sr-only">Checkbox</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -206,8 +239,8 @@
|
||||
|
||||
<td class="size-px whitespace-nowrap">
|
||||
<div class="ps-6 py-3">
|
||||
<label for="hs-at-with-checkboxes-1" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-1">
|
||||
<label for="hs-at-with-checkboxes-2" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-2" data-hs-datatable-row-selecting-individual="">
|
||||
<span class="sr-only">Checkbox</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -215,13 +248,13 @@
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
BBC News · Top Stories
|
||||
Sky News
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
https://bbci.co.uk/feeds/news/rss.xml
|
||||
https://sky.co.uk/feeds/news/rss.xml
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -262,8 +295,8 @@
|
||||
|
||||
<td class="size-px whitespace-nowrap">
|
||||
<div class="ps-6 py-3">
|
||||
<label for="hs-at-with-checkboxes-1" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-1">
|
||||
<label for="hs-at-with-checkboxes-3" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-3" data-hs-datatable-row-selecting-individual="">
|
||||
<span class="sr-only">Checkbox</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -271,13 +304,13 @@
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
BBC News · Top Stories
|
||||
Fox News
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
https://bbci.co.uk/feeds/news/rss.xml
|
||||
https://fox.com/feeds/news/rss.xml
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -313,6 +346,62 @@
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr class="bg-white hover:bg-gray-50 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
|
||||
<td class="size-px whitespace-nowrap">
|
||||
<div class="ps-6 py-3">
|
||||
<label for="hs-at-with-checkboxes-1" class="flex">
|
||||
<input type="checkbox" class="shrink-0 border-gray-300 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-600 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-at-with-checkboxes-1" data-hs-datatable-row-selecting-individual="">
|
||||
<span class="sr-only">Checkbox</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
News Agency 40
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<a href="#" class="block p-6 text-blue-500 hover:text-blue-600 focus:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">
|
||||
https://news.co.uk/feeds/news/rss.xml
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<div class="p-6">
|
||||
<span class="text-sm text-gray-500 dark:text-neutral-500">
|
||||
30th, Jan 2025
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
<div class="p-6">
|
||||
<span class="py-1 px-1.5 inline-flex items-center gap-x-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded-full dark:bg-yellow-500/10 dark:text-yellow-500">
|
||||
<svg class="size-2.5" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"></path>
|
||||
</svg>
|
||||
Warning
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<!-- <tr class="bg-white hover:bg-gray-50 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
<td class="size-px whitespace-nowrap align-top">
|
||||
@ -424,4 +513,6 @@
|
||||
<!-- End Card -->
|
||||
|
||||
</div>
|
||||
<!-- End Table Section -->
|
||||
<!-- End Table Section -->
|
||||
|
||||
<% block("scripts").append('<script src="/static/js/guild/subscriptions.js"></script>'); %>
|
@ -16,9 +16,10 @@
|
||||
</div>
|
||||
<!-- End Content -->
|
||||
|
||||
<script src="/static/foreign/preline.js"></script>
|
||||
<script src="/static/foreign/jquery.js"></script>
|
||||
<script src="/static/foreign/dataTables.js"></script>
|
||||
<script src="/static/foreign/preline.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
<%- block("scripts").toString() %>
|
||||
</body>
|
||||
</html>
|
4
src/db/db.ts
Normal file
4
src/db/db.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import knex from "knex";
|
||||
import knexConfig from "@server/../knexfile";
|
||||
|
||||
export const db = knex(knexConfig);
|
@ -0,0 +1,19 @@
|
||||
import type { Knex } from "knex";
|
||||
|
||||
const TABLE = "subscriptions";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable(TABLE, table => {
|
||||
table.increments("id").primary();
|
||||
table.string("name").notNullable();
|
||||
table.string("url").notNullable();
|
||||
table.string("guild_id").notNullable();
|
||||
table.boolean("active").notNullable().defaultTo(true);
|
||||
table.timestamps(true, true);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TABLE);
|
||||
}
|
||||
|
11
src/db/models/subs.model.ts
Normal file
11
src/db/models/subs.model.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Url } from "url";
|
||||
|
||||
export interface Subscription {
|
||||
id: number;
|
||||
name: string;
|
||||
url: Url;
|
||||
guild_id: string;
|
||||
active: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
17
src/db/seeds/test_sub.ts
Normal file
17
src/db/seeds/test_sub.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
const TABLE = "subscriptions";
|
||||
|
||||
export async function seed(knex: Knex): Promise<void> {
|
||||
// Deletes ALL existing entries
|
||||
await knex(TABLE).del();
|
||||
|
||||
// Inserts seed entries
|
||||
await knex(TABLE).insert([
|
||||
{ name: "My First Subscription", url: "https://bbci.co.uk/feeds/news.xml", guild_id: "899773845223927878", active: true },
|
||||
{ name: "My Second Sub", url: "https://bbci.co.uk/feeds/news.xml", guild_id: "899773845223927878", active: true },
|
||||
{ name: "My Third Sub", url: "https://bbci.co.uk/feeds/news.xml", guild_id: "1204426362794811453", active: true },
|
||||
{ name: "My Fourth Sub", url: "https://bbci.co.uk/feeds/news.xml", guild_id: "899773845223927878", active: true },
|
||||
{ name: "My Fith Sub", url: "https://bbci.co.uk/feeds/news.xml", guild_id: "1204426362794811453", active: true },
|
||||
]);
|
||||
};
|
47
src/knexfile.ts
Normal file
47
src/knexfile.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import path from "path";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config(); // I env vars are already loaded, but this file is invoked directly sometimes.
|
||||
|
||||
const {
|
||||
DB_CLIENT = "sqlite", // Default:
|
||||
SQLITE_FILE = path.resolve(__dirname, "../db.sqlite"), // use sqlite if not specified
|
||||
PG_HOST = "",
|
||||
PG_PORT = "",
|
||||
PG_USER = "",
|
||||
PG_PASSWORD = "",
|
||||
PG_DATABASE = ""
|
||||
} = process.env;
|
||||
|
||||
const dbConfig = {
|
||||
sqlite: {
|
||||
client: "sqlite3",
|
||||
connection: {
|
||||
filename: SQLITE_FILE
|
||||
},
|
||||
useNullAsDefault: true
|
||||
},
|
||||
postgresql: {
|
||||
client: "pg",
|
||||
connection: {
|
||||
host: PG_HOST,
|
||||
port: PG_PORT,
|
||||
user: PG_USER,
|
||||
password: PG_PASSWORD,
|
||||
database: PG_DATABASE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const knexConfig = {
|
||||
...dbConfig[DB_CLIENT as keyof typeof dbConfig],
|
||||
migrations: {
|
||||
tableName: "knex_migrations",
|
||||
directory: path.resolve(__dirname, "./db/migrations")
|
||||
},
|
||||
seeds: {
|
||||
directory: path.resolve(__dirname, "./db/seeds")
|
||||
}
|
||||
}
|
||||
|
||||
export default knexConfig;
|
@ -50,3 +50,5 @@ export const setupPassport = (passport: PassportStatic) => {
|
||||
done(null, user);
|
||||
});
|
||||
}
|
||||
|
||||
export default { get, authenticate, logout };
|
@ -14,4 +14,6 @@ export const get = async (request: Request, response: Response) => {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default { get };
|
@ -14,4 +14,6 @@ export const get = async (request: Request, response: Response) => {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default { get };
|
@ -14,4 +14,6 @@ export const get = async (request: Request, response: Response) => {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default { get };
|
@ -14,4 +14,6 @@ export const get = async (request: Request, response: Response) => {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default { get };
|
29
src/server/controllers/guild/sub.api.controller.ts
Normal file
29
src/server/controllers/guild/sub.api.controller.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Subscription } from "@db/models/subs.model";
|
||||
import { db } from "@db/db";
|
||||
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
try {
|
||||
const subscriptions = await db<Subscription>("subscriptions")
|
||||
.select("*")
|
||||
.where({ guild_id: request.params.guildId });
|
||||
|
||||
response.json(subscriptions);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
response.status(500).json({ error: "Failed to fetch subscriptions" });
|
||||
}
|
||||
}
|
||||
|
||||
export const post = async (request: Request, response: Response) => {
|
||||
try {
|
||||
//
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
response.status(500).json({ error: "Failed to create subscription" });
|
||||
}
|
||||
}
|
||||
|
||||
export default { get, post }
|
@ -14,4 +14,6 @@ export const get = async (request: Request, response: Response) => {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default { get }
|
@ -5,3 +5,5 @@ export const get = async (_request: Request, response: Response) => {
|
||||
title: "Dashboard - Relay"
|
||||
});
|
||||
};
|
||||
|
||||
export default { get };
|
@ -1,34 +0,0 @@
|
||||
const knex = require("knex");
|
||||
|
||||
const {
|
||||
DB_CLIENT,
|
||||
SQLITE_FILE,
|
||||
PG_HOST,
|
||||
PG_PORT,
|
||||
PG_USER,
|
||||
PG_PASSWORD,
|
||||
PG_DATABASE
|
||||
} = process.env;
|
||||
|
||||
const dbConfig = {
|
||||
sqlite: {
|
||||
client: "sqlite3",
|
||||
connection: {
|
||||
filename: SQLITE_FILE
|
||||
},
|
||||
useNullAsDefault: true
|
||||
},
|
||||
postgresql: {
|
||||
client: "pg",
|
||||
connection: {
|
||||
host: PG_HOST,
|
||||
port: PG_PORT,
|
||||
user: PG_USER,
|
||||
password: PG_PASSWORD,
|
||||
database: PG_DATABASE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = knex(dbConfig[DB_CLIENT]);
|
||||
module.exports = db;
|
@ -1,9 +1,11 @@
|
||||
import { Router } from "express";
|
||||
import { ensureAuthenticated, forwardAuthenticated } from "@server/middleware/authenticated";
|
||||
import * as controller from "@server/controllers/auth";
|
||||
import controller from "@server/controllers/auth.web.controller";
|
||||
|
||||
export const router = Router();
|
||||
const router = Router();
|
||||
|
||||
router.get("/login", forwardAuthenticated, controller.get);
|
||||
router.get("/logout", ensureAuthenticated, controller.logout);
|
||||
router.get("/api", forwardAuthenticated, controller.authenticate);
|
||||
router.get("/api", forwardAuthenticated, controller.authenticate);
|
||||
|
||||
export default router;
|
9
src/server/routes/guild.api.routes.ts
Normal file
9
src/server/routes/guild.api.routes.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import subApiController from "@server/controllers/guild/sub.api.controller";
|
||||
|
||||
const router = Router();
|
||||
router.get("/:guildId/subscriptions/api", subApiController.get);
|
||||
router.post("/:guildId/subscriptions/api", subApiController.post);
|
||||
|
||||
export default router;
|
@ -1,15 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import * as overviewController from "@server/controllers/guild/overview";
|
||||
import * as subscriptionController from "@server/controllers/guild/subscriptions";
|
||||
import * as filterController from "@server/controllers/guild/filters";
|
||||
import * as styleController from "@server/controllers/guild/styles";
|
||||
import * as contentController from "@server/controllers/guild/content";
|
||||
import { getGuildPage } from "@server/middleware/guildPage";
|
||||
|
||||
export const router = Router();
|
||||
|
||||
router.get("/:guildId", getGuildPage, overviewController.get);
|
||||
router.get("/:guildId/subscriptions", getGuildPage, subscriptionController.get);
|
||||
router.get("/:guildId/filters", getGuildPage, filterController.get);
|
||||
router.get("/:guildId/styles", getGuildPage, styleController.get);
|
||||
router.get("/:guildId/content", getGuildPage, contentController.get);
|
17
src/server/routes/guild.web.routes.ts
Normal file
17
src/server/routes/guild.web.routes.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { getGuildPage } from "@server/middleware/guildPage";
|
||||
import indexWebController from "@server/controllers/guild/index.web.controller";
|
||||
import subWebController from "@server/controllers/guild/sub.web.controller";
|
||||
import filterWebController from "@server/controllers/guild/filter.web.controller";
|
||||
import styleWebController from "@server/controllers/guild/style.web.controller";
|
||||
import contentWebController from "@server/controllers/guild/content.web.controller";
|
||||
|
||||
const router = Router();
|
||||
router.get("/:guildId", getGuildPage, indexWebController.get);
|
||||
router.get("/:guildId/subscriptions", getGuildPage, subWebController.get);
|
||||
router.get("/:guildId/filters", getGuildPage, filterWebController.get);
|
||||
router.get("/:guildId/style", getGuildPage, styleWebController.get);
|
||||
router.get("/:guildId/content", getGuildPage, contentWebController.get);
|
||||
|
||||
export default router;
|
@ -1,6 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import * as controller from "@server/controllers/home";
|
||||
|
||||
export const router = Router();
|
||||
|
||||
router.get("/", controller.get);
|
8
src/server/routes/home.web.routes.ts
Normal file
8
src/server/routes/home.web.routes.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Router } from "express";
|
||||
import controller from "@server/controllers/home.web.controller";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", controller.get);
|
||||
|
||||
export default router;
|
@ -30,13 +30,11 @@
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
"@/*": ["src/*"],
|
||||
"@db/*": ["src/db/*"],
|
||||
"@server/*": ["src/server/*"],
|
||||
"@client/*": ["src/client/*"],
|
||||
"@bot/*": ["src/bot/*"],
|
||||
"@utils/*": ["src/utils/*"],
|
||||
"@views/*": ["src/client/views/*"],
|
||||
"@public/*": ["src/client/public/*"],
|
||||
"@node_modules/*": ["node_modules/*"],
|
||||
},
|
||||
"plugins": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user