new selfhosted version

This commit is contained in:
antonio
2025-11-28 14:11:51 +01:00
parent afda29997d
commit 951860f67e
1046 changed files with 72586 additions and 574750 deletions

View File

@@ -0,0 +1,19 @@
import { PremiumModel } from '~/shared/schema/PremiumSchema';
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event);
const { user_id } = ctx;
const premium = await PremiumModel.findOne({ user_id })
if (!premium) throw createError({ status: 400, message: 'Error getting user. Please contact support.' });
const { payments } = useTRPC();
const customer = await payments.customer.get.query({ user_id })
return customer;
});

View File

@@ -0,0 +1,78 @@
import { ProjectModel } from "@schema/project/ProjectSchema";
import { ProjectCountModel } from "@schema/project/ProjectsCounts";
import { UserLimitModel } from "@schema/UserLimitSchema";
import { UserSettingsModel } from "@schema/UserSettings";
import { AiChatModel } from "@schema/ai/AiChatSchema";
import { LimitNotifyModel } from "@schema/broker/LimitNotifySchema";
import { SessionModel } from "@schema/metrics/SessionSchema";
import { UserModel } from "@schema/UserSchema";
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
import { TeamMemberModel } from "~/shared/schema/TeamMemberSchema";
import { PasswordModel } from "~/shared/schema/PasswordSchema";
import { PremiumModel } from "~/shared/schema/PremiumSchema";
import { VisitModel } from "~/shared/schema/metrics/VisitSchema";
import { EventModel } from "~/shared/schema/metrics/EventSchema";
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event);
const projects = await ProjectModel.find({ owner: ctx.user_id });
const premium = await PremiumModel.findOne({ user_id: ctx.user_id });
if (!premium) throw createError({ status: 400, message: 'Cannot find premium model. Please contact support.' });
if (premium.premium_type !== 0 && premium.premium_type !== 7006 && premium.premium_type !== 7999) {
throw createError({ status: 400, message: 'Cannot delete an account with a premium project' });
}
const membersDeletation = await TeamMemberModel.deleteMany({ user_id: ctx.user_id });
const membersEmailDeletation = await TeamMemberModel.deleteMany({ email: ctx.user_email });
const passwordDeletation = await PasswordModel.deleteMany({ user_id: ctx.user_id });
const limitdeletation = await UserLimitModel.deleteMany({ user_id: ctx.user_id });
const notifiesDeletation = await LimitNotifyModel.deleteMany({ user_id: ctx.user_id });
try {
const { payments } = useTRPC();
if (premium) await payments.customer.delete.mutate({ customer_id: premium.customer_id });
} catch (ex) {
console.error(ex);
}
for (const project of projects) {
const project_id = project._id;
const projectDeletation = await ProjectModel.deleteOne({ _id: project_id });
const userSettingsDeletation = await UserSettingsModel.deleteOne({ project_id });
const countDeletation = await ProjectCountModel.deleteMany({ project_id });
const sessionsDeletation = SessionModel.deleteMany({ project_id });
const visitsDeletation = VisitModel.deleteMany({ project_id });
const eventsDeletation = EventModel.deleteMany({ project_id });
const aiChatsDeletation = AiChatModel.deleteMany({ project_id });
//Shields
const addressBlacklistDeletation = AddressBlacklistModel.deleteMany({ project_id });
const botTrafficOptionsDeletation = BotTrafficOptionModel.deleteMany({ project_id });
const countryBlacklistDeletation = CountryBlacklistModel.deleteMany({ project_id });
const domainWhitelistDeletation = DomainWhitelistModel.deleteMany({ project_id });
}
const premiumDeletation = await PremiumModel.deleteOne({ user_id: ctx.user_id });
const userDeletation = await UserModel.deleteOne({ _id: ctx.user_id });
return { ok: true };
});

View File

@@ -1,40 +0,0 @@
import { ProjectModel } from "@schema/project/ProjectSchema";
import { ProjectCountModel } from "@schema/project/ProjectsCounts";
import { ProjectLimitModel } from "@schema/project/ProjectsLimits";
import { UserSettingsModel } from "@schema/UserSettings";
import { AiChatModel } from "@schema/ai/AiChatSchema";
import { LimitNotifyModel } from "@schema/broker/LimitNotifySchema";
import { SessionModel } from "@schema/metrics/SessionSchema";
import StripeService from "~/server/services/StripeService";
import { UserModel } from "@schema/UserSchema";
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return;
const projects = await ProjectModel.find({ owner: userData.id });
const premiumProjects = projects.filter(e => { return e.premium && e.premium_type != 0 }).length;
if (premiumProjects > 0) return setResponseStatus(event, 400, 'Cannot delete an account with a premium project');
for (const project of projects) {
const project_id = project._id;
await StripeService.deleteCustomer(project.customer_id);
const projectDeletation = await ProjectModel.deleteOne({ _id: project_id });
const userSettingsDeletation = await UserSettingsModel.deleteOne({ project_id });
const countDeletation = await ProjectCountModel.deleteMany({ project_id });
const limitdeletation = await ProjectLimitModel.deleteMany({ project_id });
const sessionsDeletation = await SessionModel.deleteMany({ project_id });
const notifiesDeletation = await LimitNotifyModel.deleteMany({ project_id });
const aiChatsDeletation = await AiChatModel.deleteMany({ project_id });
const userDeletation = await UserModel.deleteOne({ _id: userData.id });
}
return { ok: true };
});

View File

@@ -0,0 +1,33 @@
import z from 'zod';
import { PasswordModel } from '~/shared/schema/PasswordSchema';
import { UserModel } from '~/shared/schema/UserSchema';
import crypto from 'crypto';
const ZResetPasswordBody = z.object({
email: z.string().email()
})
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'flag:allowAnon');
const { email } = await readValidatedBody(event, ZResetPasswordBody.parse);
const user = await UserModel.findOne({ email });
if (!user) return { ok: true };
const pass = await PasswordModel.findOne({ email });
if (!pass) throw createError({ status: 400, message: 'The account is associated to a Social Login. You cannot reset the password.' });
const { BASE_URL, RESET_PASSWORD_SECRET } = useRuntimeConfig();
const hash = crypto.createHash('sha256');
const authenticationCode = hash.update(`${RESET_PASSWORD_SECRET}:${email}`).digest('hex');
const link = `${BASE_URL}/reset_password?code=${authenticationCode}&mail=${Buffer.from(email).toString('base64')}`;
const tRpc = useTRPC();
await tRpc.emails.email.sendResetPasswordEmail.mutate({ email, link })
return { ok: true }
});

View File

@@ -0,0 +1,18 @@
import { PremiumModel } from '~/shared/schema/PremiumSchema';
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event);
const { user_id } = ctx;
const premium = await PremiumModel.findOne({ user_id })
if (!premium) throw createError({ status: 400, message: 'Error getting user. Please contact support.' });
const { payments } = useTRPC();
const invoices = await payments.invoice.invoices.query({ user_id })
return invoices;
});

View File

@@ -1,9 +0,0 @@
import { ProjectModel } from "@schema/project/ProjectSchema";
import { AuthContext } from "~/server/middleware/01-authorization";
export default defineEventHandler(async event => {
const userData: AuthContext = getRequestUser(event) as any;
if (!userData.logged) return;
const userProjects = await ProjectModel.countDocuments({ owner: userData.id });
return userProjects == 0;
});

View File

@@ -1,10 +0,0 @@
import { UserSettingsModel } from "@schema/UserSettings";
import { AuthContext } from "~/server/middleware/01-authorization";
export default defineEventHandler(async event => {
const userData: AuthContext = getRequestUser(event) as any;
if (!userData.logged) return;
// const userSettings = await UserSettingsModel.findOne({ user_id: userData.id }, { max_projects: 1 });
// return userSettings?.max_projects || 3;
return 20;
});

View File

@@ -1,6 +1,21 @@
import { AuthContext } from "~/server/middleware/01-authorization";
import { PasswordModel } from "~/shared/schema/PasswordSchema";
export type TUserMe = {
email: string,
name: string,
email_login: boolean
}
export default defineEventHandler(async event => {
const userData: AuthContext = getRequestUser(event) as any;
return userData;
});
const { user, v, secure } = await requireUserSession(event);
const hasPassword = await PasswordModel.exists({ email: user.email });
const result: TUserMe = {
email: user.email,
name: user.name,
email_login: hasPassword != null
}
return result;
});

View File

@@ -0,0 +1,12 @@
import { OnboardingModel } from '@schema/OnboardingSchema';
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'flag:allowAnonRegistered');
const exists = await OnboardingModel.exists({ user_id: ctx.user_id });
return { exists: exists != null }
});

View File

@@ -0,0 +1,18 @@
import { OnboardingModel } from '@schema/OnboardingSchema';
export default defineEventHandler(async event => {
//TODO: SELFHOST - Disable onboarding
const ctx = await getRequestContext(event);
const { job, analytics } = await readBody(event);
await OnboardingModel.updateOne({
user_id: ctx.user_id,
}, { job, analytics }, { upsert: true });
return { ok: true }
});

View File

@@ -1,10 +0,0 @@
import { PasswordModel } from "@schema/PasswordSchema";
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return;
const hasPassword = await PasswordModel.exists({ email: userData.user.email });
if (hasPassword) return { can_change: true };
return { can_change: false }
});

View File

@@ -1,33 +0,0 @@
import crypto from 'crypto';
import { PasswordModel } from '@schema/PasswordSchema';
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return;
const { old_password, new_password } = await readBody(event);
if (new_password.length < 6) return { error: true, message: 'Password too short' }
const target = await PasswordModel.findOne({ email: userData.user.email });
if (!target) return { error: true, message: 'Internal error. User not found.' }
const hashOld = crypto.createHash('sha256');
const hashedPasswordOld = hashOld.update(old_password + '_litlyx').digest('hex');
if (target.password !== hashedPasswordOld) {
return { error: true, message: 'Old password not correct' }
}
const hashNew = crypto.createHash('sha256');
const hashedPasswordNew = hashNew.update(new_password + '_litlyx').digest('hex');
target.password = hashedPasswordNew;
await target.save();
return { error: false, message: 'Success' }
});

View File

@@ -1,28 +0,0 @@
import crypto from 'crypto';
import { PasswordModel } from '@schema/PasswordSchema';
import { EmailService } from '@services/EmailService'
import { EmailServiceHelper } from '~/server/services/EmailServiceHelper';
export default defineEventHandler(async event => {
const { email } = await readBody(event);
const target = await PasswordModel.findOne({ email });
if (!target) return { error: true, message: 'Internal error. User not found.' }
const newPass = crypto.randomBytes(5).toString('hex');
const hash = crypto.createHash('sha256');
const hashedPassword = hash.update(newPass + '_litlyx').digest('hex');
target.password = hashedPassword;
await target.save();
const emailData = EmailService.getEmailServerInfo('reset_password', { target: email, newPassword: newPass });
EmailServiceHelper.sendEmail(emailData);
return { error: false, message: 'Password changed' }
});

View File

@@ -0,0 +1,41 @@
import { getPlanFromId, PLAN_DATA } from "~/shared/data/PLANS";
import { PasswordModel } from "~/shared/schema/PasswordSchema";
import { PremiumModel } from "~/shared/schema/PremiumSchema";
import { UserLimitModel } from "~/shared/schema/UserLimitSchema";
export type TUserPlanInfo = {
premium: boolean,
premium_type: number,
limit: number,
count: number,
start_at: number,
end_at: number,
payment_failed: boolean,
canceled: boolean
}
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event);
const { user_id } = ctx;
const premium = await PremiumModel.findOne({ user_id });
if (!premium) throw createError({ status: 400, message: 'Error getting plan. Please contact support.' });
const limits = await UserLimitModel.findOne({ user_id });
if (!limits) throw createError({ status: 400, message: 'Error getting plan. Please contact support.' });
const result: TUserPlanInfo = {
premium: premium.premium_type > 0,
premium_type: premium.premium_type,
limit: limits.limit,
count: limits.visits + limits.events,
start_at: new Date(limits.billing_start_at).getTime(),
end_at: new Date(limits.billing_expire_at).getTime(),
payment_failed: premium.payment_failed ?? false,
canceled: premium.plan_cancelled ?? false
}
return result;
});

View File

@@ -0,0 +1,31 @@
import z from 'zod';
import { PasswordModel } from '~/shared/schema/PasswordSchema';
import crypto from 'crypto';
const ZResetPasswordBody = z.object({
email: z.string().email(),
password: z.string().min(6).max(64),
jwt: z.string()
})
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event, 'flag:allowAnon');
const { email, password, jwt } = await readValidatedBody(event, ZResetPasswordBody.parse);
const { RESET_PASSWORD_SECRET } = useRuntimeConfig();
const readHash = crypto.createHash('sha256');
const hashedSecret = readHash.update(`${RESET_PASSWORD_SECRET}:${email}`).digest('hex');
const ok = hashedSecret === jwt;
if (!ok) throw createError({ status: 400, message: 'Error during password set. Please contact support.' });
const userNewPassword = await hashPassword(password)
await PasswordModel.updateOne({ email }, { password: userNewPassword });
return { ok: true }
});

View File

@@ -0,0 +1,18 @@
import { PremiumModel } from '~/shared/schema/PremiumSchema';
export default defineEventHandler(async event => {
const ctx = await getRequestContext(event);
const { user_id } = ctx;
const premium = await PremiumModel.findOne({ user_id })
if (!premium) throw createError({ status: 400, message: 'Error getting user. Please contact support.' });
const body = await readBody(event);
const { payments } = useTRPC();
const result = await payments.customer.update.mutate({ user_id, address: body });
return result;
});