mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-29 20:12:28 +03:00
Adds login for stats
This commit is contained in:
parent
058d226be8
commit
cf9b85466e
@ -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
|
||||||
|
19
wasp-ai/migrations/20230703150042_add_user/migration.sql
Normal file
19
wasp-ai/migrations/20230703150042_add_user/migration.sql
Normal 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;
|
@ -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");
|
@ -28,7 +28,6 @@ function generateLast24HoursData(projects) {
|
|||||||
buckets[reverseBucketIndex].count++;
|
buckets[reverseBucketIndex].count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(buckets);
|
|
||||||
return buckets;
|
return buckets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
wasp-ai/src/client/pages/LoginPage.jsx
Normal file
9
wasp-ai/src/client/pages/LoginPage.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { LoginForm } from "@wasp/auth/forms/Login";
|
||||||
|
|
||||||
|
export function LoginPage() {
|
||||||
|
return (
|
||||||
|
<div className="big-box">
|
||||||
|
<LoginForm />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -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:{" "}
|
||||||
|
15
wasp-ai/src/server/auth.ts
Normal file
15
wasp-ai/src/server/auth.ts
Normal 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"],
|
||||||
|
};
|
||||||
|
};
|
@ -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: {
|
||||||
|
Loading…
Reference in New Issue
Block a user