Adds login for stats

This commit is contained in:
Mihovil Ilakovac 2023-07-03 17:26:17 +02:00
parent 058d226be8
commit cf9b85466e
8 changed files with 110 additions and 5 deletions

View File

@ -25,13 +25,24 @@ app waspAi {
("@visx/scale", "3.2.0"), ("@visx/scale", "3.2.0"),
("@visx/responsive", "3.0.0"), ("@visx/responsive", "3.0.0"),
("@visx/gradient", "3.0.0"), ("@visx/gradient", "3.0.0"),
("@visx/axis", "3.2.0") ("@visx/axis", "3.2.0"),
], ],
client: { client: {
rootComponent: import { RootComponent } from "@client/RootComponent.jsx", rootComponent: import { RootComponent } from "@client/RootComponent.jsx",
}, },
db: { db: {
system: PostgreSQL system: PostgreSQL
},
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
configFn: import { getGoogleAuthConfig } from "@server/auth.js",
getUserFieldsFn: import { getUserFields } from "@server/auth.js",
}
},
onAuthFailedRedirectTo: "/login"
} }
} }
@ -47,7 +58,13 @@ page ResultPage {
route StatsRoute { path: "/stats", to: StatsPage } route StatsRoute { path: "/stats", to: StatsPage }
page StatsPage { page StatsPage {
component: import { Stats } from "@client/pages/StatsPage.jsx" component: import { Stats } from "@client/pages/StatsPage.jsx",
authRequired: true
}
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@client/pages/LoginPage.jsx",
} }
action startGeneratingNewApp { action startGeneratingNewApp {
@ -73,6 +90,23 @@ query getStats {
] ]
} }
entity User {=psl
id Int @id @default(autoincrement())
email String @unique
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id String @id @default(uuid())
provider String
providerId String
userId Int
user User @relation(fields: [userId], references: [id])
psl=}
entity Project {=psl entity Project {=psl
id String @id @default(uuid()) id String @id @default(uuid())
name String name String

View File

@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SocialLogin" (
"id" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "SocialLogin_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "SocialLogin" ADD CONSTRAINT "SocialLogin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,12 @@
/*
Warnings:
- A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
- Added the required column `email` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "email" TEXT NOT NULL;
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@ -28,7 +28,6 @@ function generateLast24HoursData(projects) {
buckets[reverseBucketIndex].count++; buckets[reverseBucketIndex].count++;
} }
}); });
console.log(buckets);
return buckets; return buckets;
} }

View File

@ -0,0 +1,9 @@
import { LoginForm } from "@wasp/auth/forms/Login";
export function LoginPage() {
return (
<div className="big-box">
<LoginForm />
</div>
);
}

View File

@ -8,6 +8,7 @@ import { StatusPill } from "../components/StatusPill";
import { BarChart } from "../components/BarChart"; import { BarChart } from "../components/BarChart";
import ParentSize from "@visx/responsive/lib/components/ParentSize"; import ParentSize from "@visx/responsive/lib/components/ParentSize";
import { poolOfExampleIdeas } from "../examples"; import { poolOfExampleIdeas } from "../examples";
import logout from "@wasp/auth/logout";
export function Stats() { export function Stats() {
const [filterOutExampleApps, setFilterOutExampleApps] = useState(true); const [filterOutExampleApps, setFilterOutExampleApps] = useState(true);
@ -64,13 +65,24 @@ export function Stats() {
return ( return (
<div className="big-box"> <div className="big-box">
<h1 className="text-3xl font-semibold text-slate-800 mb-4">Stats</h1> <div className="flex justify-between items-center mb-4">
<h1 className="text-3xl font-semibold text-slate-800">Stats</h1>
<div>
<button className="button sm" onClick={logout}>Logout</button>
</div>
</div>
{isLoading && <p>Loading...</p>} {isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>} {error && <p>Error: {error.message}</p>}
{stats && ( {stats && filteredStats.length === 0 && (
<p className="text-sm text-slate-500">
No projects created yet.
</p>
)}
{stats && filteredStats.length > 0 && (
<> <>
<p className="text-sm text-slate-500 mb-2"> <p className="text-sm text-slate-500 mb-2">
Number of projects created in the last 24 hours:{" "} Number of projects created in the last 24 hours:{" "}

View File

@ -0,0 +1,15 @@
import { GetUserFieldsFn } from "@wasp/types";
export const getUserFields: GetUserFieldsFn = async (_context, args) => {
return {
email: args.profile.emails[0].value,
};
};
export const getGoogleAuthConfig = () => {
return {
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
scope: ["profile", "email"],
};
};

View File

@ -198,6 +198,11 @@ export const getAppGenerationResult = (async (args, context) => {
}>; }>;
export const getStats = (async (args, context) => { export const getStats = (async (args, context) => {
const emailsWhitelist = process.env.ADMIN_EMAILS_WHITELIST?.split(",") || [];
if (!context.user || !emailsWhitelist.includes(context.user.email)) {
throw new HttpError(401, "Only admins can access stats.");
}
const { Project } = context.entities; const { Project } = context.entities;
const projects = await Project.findMany({ const projects = await Project.findMany({
orderBy: { orderBy: {