onboarding completed

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-04-16 11:41:15 +02:00
committed by Carl-Gerhard Lindesvärd
parent 97627583ec
commit 7d22d2ddad
79 changed files with 2542 additions and 805 deletions

View File

@@ -9,6 +9,7 @@
},
"dependencies": {
"ramda": "^0.29.1",
"superjson": "^1.13.3",
"unique-names-generator": "^4.7.1"
},
"devDependencies": {

View File

@@ -1,4 +1,5 @@
import { anyPass, assocPath, isEmpty, isNil, reject } from 'ramda';
import superjson from 'superjson';
export function toDots(
obj: Record<string, unknown>,
@@ -38,3 +39,16 @@ export function getSafeJson<T>(str: string): T | null {
return null;
}
}
export function getSuperJson<T>(str: string): T | null {
const json = getSafeJson<T>(str);
if (
typeof json === 'object' &&
json !== null &&
'json' in json &&
'meta' in json
) {
return superjson.parse<T>(str);
}
return json;
}

View File

@@ -2,6 +2,12 @@ import { isSameDay, isSameMonth } from 'date-fns';
export const NOT_SET_VALUE = '(not set)';
export const ProjectTypeNames = {
website: 'Website',
app: 'App',
backend: 'Backend',
} as const;
export const operators = {
is: 'Is',
isNot: 'Is not',

View File

@@ -21,6 +21,7 @@
"@prisma/client": "^5.1.1",
"ramda": "^0.29.1",
"sqlstring": "^2.3.3",
"superjson": "^1.13.3",
"uuid": "^9.0.1"
},
"devDependencies": {

View File

@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "ProjectType" AS ENUM ('website', 'app', 'backend');
-- AlterTable
ALTER TABLE "projects" ADD COLUMN "types" "ProjectType"[] DEFAULT ARRAY[]::"ProjectType"[];

View File

@@ -10,11 +10,18 @@ datasource db {
url = env("DATABASE_URL")
}
enum ProjectType {
website
app
backend
}
model Project {
id String @id @default(dbgenerated("gen_random_uuid()"))
id String @id @default(dbgenerated("gen_random_uuid()"))
name String
organizationSlug String
eventsCount Int @default(0)
eventsCount Int @default(0)
types ProjectType[] @default([])
events Event[]
profiles Profile[]

View File

@@ -1,5 +1,6 @@
import { omit, uniq } from 'ramda';
import { escape } from 'sqlstring';
import superjson from 'superjson';
import { v4 as uuid } from 'uuid';
import { randomSplitName, toDots } from '@openpanel/common';
@@ -113,11 +114,51 @@ export interface IServiceCreateEventPayload {
meta: EventMeta | undefined;
}
export interface IServiceEventMinimal {
id: string;
name: string;
projectId: string;
sessionId: string;
createdAt: Date;
country?: string | undefined;
os?: string | undefined;
browser?: string | undefined;
device?: string | undefined;
brand?: string | undefined;
duration: number;
path: string;
referrer: string | undefined;
meta: EventMeta | undefined;
minimal: boolean;
}
interface GetEventsOptions {
profile?: boolean | Prisma.ProfileSelect;
meta?: boolean | Prisma.EventMetaSelect;
}
export function transformMinimalEvent(
event: IServiceCreateEventPayload
): IServiceEventMinimal {
return {
id: event.id,
name: event.name,
projectId: event.projectId,
sessionId: event.sessionId,
createdAt: event.createdAt,
country: event.country,
os: event.os,
browser: event.browser,
device: event.device,
brand: event.brand,
duration: event.duration,
path: event.path,
referrer: event.referrer,
meta: event.meta,
minimal: true,
};
}
export async function getLiveVisitors(projectId: string) {
const keys = await redis.keys(`live:event:${projectId}:*`);
return keys.length;
@@ -227,7 +268,7 @@ export async function createEvent(
},
});
redisPub.publish('event', JSON.stringify(transformEvent(event)));
redisPub.publish('event', superjson.stringify(transformEvent(event)));
redis.set(
`live:event:${event.project_id}:${event.profile_id}`,
'',

View File

@@ -23,8 +23,9 @@ export function transformOrganization(org: Organization) {
export async function getCurrentOrganizations() {
const session = auth();
if (!session.userId) return [];
const organizations = await clerkClient.users.getOrganizationMembershipList({
userId: session.userId!,
userId: session.userId,
});
return organizations.map((item) => transformOrganization(item.organization));
}

View File

@@ -1,9 +1,14 @@
import { auth } from '@clerk/nextjs';
import type { Project } from '../prisma-client';
import type { Prisma, Project } from '../prisma-client';
import { db } from '../prisma-client';
export type IServiceProject = Project;
export type IServiceProjectWithClients = Prisma.ProjectGetPayload<{
include: {
clients: true;
};
}>;
export async function getProjectById(id: string) {
const res = await db.project.findUnique({
@@ -19,6 +24,23 @@ export async function getProjectById(id: string) {
return res;
}
export async function getProjectWithClients(id: string) {
const res = await db.project.findUnique({
where: {
id,
},
include: {
clients: true,
},
});
if (!res) {
return null;
}
return res;
}
export async function getProjectsByOrganizationSlug(organizationSlug: string) {
return db.project.findMany({
where: {

View File

@@ -0,0 +1,67 @@
const api = {
logo: 'https://cdn-icons-png.flaticon.com/512/10169/10169724.png',
name: 'Rest API',
href: 'https://docs.openpanel.dev/docs/api',
} as const;
export const frameworks = {
website: [
{
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/240px-HTML5_logo_and_wordmark.svg.png',
name: 'HTML / Script',
href: 'https://docs.openpanel.dev/docs/script',
},
{
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png',
name: 'React',
href: 'https://docs.openpanel.dev/docs/react',
},
{
logo: 'https://static-00.iconduck.com/assets.00/nextjs-icon-512x512-y563b8iq.png',
name: 'Next.js',
href: 'https://docs.openpanel.dev/docs/nextjs',
},
{
logo: 'https://www.datocms-assets.com/205/1642515307-square-logo.svg',
name: 'Remix',
href: 'https://docs.openpanel.dev/docs/remix',
},
{
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1024px-Vue.js_Logo_2.svg.png',
name: 'Vue',
href: 'https://docs.openpanel.dev/docs/vue',
},
{
logo: 'https://astro.build/assets/press/astro-icon-dark.png',
name: 'Astro',
href: 'https://docs.openpanel.dev/docs/astro',
},
api,
],
app: [
{
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png',
name: 'React-Native',
href: 'https://docs.openpanel.dev/docs/react-native',
},
api,
],
backend: [
{
logo: 'https://static-00.iconduck.com/assets.00/node-js-icon-454x512-nztofx17.png',
name: 'Node',
href: 'https://docs.openpanel.dev/docs/node',
},
{
logo: 'https://expressjs.com/images/favicon.png',
name: 'Express',
href: 'https://docs.openpanel.dev/docs/express',
},
{
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Laravel.svg/1969px-Laravel.svg.png',
name: 'Laravel',
href: 'https://github.com/tbleckert/openpanel-laravel/tree/main',
},
api,
],
} as const;

View File

@@ -0,0 +1 @@
export * from './frameworks';

View File

@@ -0,0 +1,27 @@
{
"name": "@openpanel/sdk-info",
"version": "0.0.1",
"main": "index.ts",
"scripts": {
"lint": "eslint .",
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env -c --"
},
"devDependencies": {
"@openpanel/eslint-config": "workspace:*",
"@openpanel/prettier-config": "workspace:*",
"@openpanel/tsconfig": "workspace:*",
"eslint": "^8.48.0",
"prettier": "^3.0.3",
"prisma": "^5.1.1",
"typescript": "^5.2.2"
},
"eslintConfig": {
"root": true,
"extends": [
"@openpanel/eslint-config/base"
]
},
"prettier": "@openpanel/prettier-config"
}

View File

@@ -0,0 +1,12 @@
{
"extends": "@openpanel/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["."],
"exclude": ["node_modules"]
}

View File

@@ -97,3 +97,36 @@ export const zCreateReference = z.object({
projectId: z.string(),
datetime: z.string(),
});
export const zOnboardingProject = z
.object({
organization: z.string().min(3),
project: z.string().min(3),
domain: z.string().url().or(z.literal('').or(z.null())),
website: z.boolean(),
app: z.boolean(),
backend: z.boolean(),
})
.superRefine((data, ctx) => {
if (data.website && !data.domain) {
ctx.addIssue({
code: 'custom',
message: 'Domain is required for website tracking',
path: ['domain'],
});
}
if (
data.website === false &&
data.app === false &&
data.backend === false
) {
['app', 'backend', 'website'].forEach((key) => {
ctx.addIssue({
code: 'custom',
message: 'At least one type must be selected',
path: [key],
});
});
}
});