mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
new selfhosted version
This commit is contained in:
19
dashboard/server/api/user/customer.ts
Normal file
19
dashboard/server/api/user/customer.ts
Normal 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;
|
||||
|
||||
});
|
||||
78
dashboard/server/api/user/delete.delete.ts
Normal file
78
dashboard/server/api/user/delete.delete.ts
Normal 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 };
|
||||
|
||||
|
||||
});
|
||||
@@ -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 };
|
||||
|
||||
|
||||
});
|
||||
33
dashboard/server/api/user/forgot_password.post.ts
Normal file
33
dashboard/server/api/user/forgot_password.post.ts
Normal 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 }
|
||||
|
||||
});
|
||||
18
dashboard/server/api/user/invoices.ts
Normal file
18
dashboard/server/api/user/invoices.ts
Normal 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;
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
12
dashboard/server/api/user/onboarding/exists.ts
Normal file
12
dashboard/server/api/user/onboarding/exists.ts
Normal 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 }
|
||||
|
||||
});
|
||||
18
dashboard/server/api/user/onboarding/send.post.ts
Normal file
18
dashboard/server/api/user/onboarding/send.post.ts
Normal 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 }
|
||||
|
||||
});
|
||||
@@ -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 }
|
||||
});
|
||||
@@ -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' }
|
||||
|
||||
});
|
||||
@@ -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' }
|
||||
|
||||
});
|
||||
41
dashboard/server/api/user/plan.ts
Normal file
41
dashboard/server/api/user/plan.ts
Normal 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;
|
||||
});
|
||||
|
||||
31
dashboard/server/api/user/set_new_password.post.ts
Normal file
31
dashboard/server/api/user/set_new_password.post.ts
Normal 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 }
|
||||
|
||||
});
|
||||
18
dashboard/server/api/user/update_customer.post.ts
Normal file
18
dashboard/server/api/user/update_customer.post.ts
Normal 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;
|
||||
});
|
||||
Reference in New Issue
Block a user