mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
5.9 KiB
254 lines
5.9 KiB
import { PrismaClient } from '@prisma/client';
|
|
import { createHmac } from 'node:crypto';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
const DEMO_USER_ID = 'demo0000-0000-0000-0000-000000000000';
|
|
const DEMO_ACCOUNT_ID = 'demo-acc0-0000-0000-000000000000';
|
|
|
|
function createAccessToken(password: string, salt: string): string {
|
|
const hash = createHmac('sha512', salt);
|
|
hash.update(password);
|
|
return hash.digest('hex');
|
|
}
|
|
|
|
async function main() {
|
|
// Create default tags
|
|
await prisma.tag.createMany({
|
|
data: [
|
|
{
|
|
id: '4452656d-9fa4-4bd0-ba38-70492e31d180',
|
|
name: 'EMERGENCY_FUND'
|
|
},
|
|
{
|
|
id: 'f2e868af-8333-459f-b161-cbc6544c24bd',
|
|
name: 'EXCLUDE_FROM_ANALYSIS'
|
|
}
|
|
],
|
|
skipDuplicates: true
|
|
});
|
|
|
|
// Create demo user for evaluators (if not already exists)
|
|
const accessTokenSalt = process.env.ACCESS_TOKEN_SALT;
|
|
if (!accessTokenSalt) {
|
|
console.log('ACCESS_TOKEN_SALT not set, skipping demo user creation');
|
|
return;
|
|
}
|
|
|
|
const existingDemoUser = await prisma.user.findUnique({
|
|
where: { id: DEMO_USER_ID }
|
|
});
|
|
|
|
if (existingDemoUser) {
|
|
console.log('Demo user already exists, skipping creation');
|
|
return;
|
|
}
|
|
|
|
console.log('Creating demo user with portfolio data...');
|
|
|
|
// Generate access token for demo user
|
|
const rawAccessToken = 'demo-access-token-for-ghostfolio-ai-eval';
|
|
const hashedAccessToken = createAccessToken(rawAccessToken, accessTokenSalt);
|
|
|
|
// Create demo user with DEMO role
|
|
await prisma.user.create({
|
|
data: {
|
|
id: DEMO_USER_ID,
|
|
accessToken: hashedAccessToken,
|
|
role: 'DEMO',
|
|
provider: 'ANONYMOUS',
|
|
settings: {
|
|
create: {
|
|
settings: {
|
|
baseCurrency: 'USD',
|
|
locale: 'en-US',
|
|
language: 'en',
|
|
viewMode: 'DEFAULT'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Create demo account
|
|
await prisma.account.create({
|
|
data: {
|
|
id: DEMO_ACCOUNT_ID,
|
|
name: 'Demo Portfolio',
|
|
currency: 'USD',
|
|
userId: DEMO_USER_ID
|
|
}
|
|
});
|
|
|
|
// Create symbol profiles for demo holdings (if not exists)
|
|
const symbols = [
|
|
{
|
|
symbol: 'AAPL',
|
|
name: 'Apple Inc.',
|
|
currency: 'USD',
|
|
dataSource: 'YAHOO',
|
|
assetClass: 'EQUITY',
|
|
assetSubClass: 'STOCK'
|
|
},
|
|
{
|
|
symbol: 'MSFT',
|
|
name: 'Microsoft Corporation',
|
|
currency: 'USD',
|
|
dataSource: 'YAHOO',
|
|
assetClass: 'EQUITY',
|
|
assetSubClass: 'STOCK'
|
|
},
|
|
{
|
|
symbol: 'VTI',
|
|
name: 'Vanguard Total Stock Market ETF',
|
|
currency: 'USD',
|
|
dataSource: 'YAHOO',
|
|
assetClass: 'EQUITY',
|
|
assetSubClass: 'ETF'
|
|
},
|
|
{
|
|
symbol: 'GOOGL',
|
|
name: 'Alphabet Inc.',
|
|
currency: 'USD',
|
|
dataSource: 'YAHOO',
|
|
assetClass: 'EQUITY',
|
|
assetSubClass: 'STOCK'
|
|
},
|
|
{
|
|
symbol: 'AMZN',
|
|
name: 'Amazon.com Inc.',
|
|
currency: 'USD',
|
|
dataSource: 'YAHOO',
|
|
assetClass: 'EQUITY',
|
|
assetSubClass: 'STOCK'
|
|
}
|
|
];
|
|
|
|
for (const s of symbols) {
|
|
await prisma.symbolProfile.upsert({
|
|
where: {
|
|
dataSource_symbol: {
|
|
dataSource: s.dataSource as any,
|
|
symbol: s.symbol
|
|
}
|
|
},
|
|
create: {
|
|
symbol: s.symbol,
|
|
name: s.name,
|
|
currency: s.currency,
|
|
dataSource: s.dataSource as any,
|
|
assetClass: s.assetClass as any,
|
|
assetSubClass: s.assetSubClass as any
|
|
},
|
|
update: {}
|
|
});
|
|
}
|
|
|
|
// Create demo activities (BUY orders)
|
|
const activities = [
|
|
{
|
|
symbol: 'AAPL',
|
|
dataSource: 'YAHOO',
|
|
quantity: 15,
|
|
unitPrice: 150,
|
|
date: new Date('2024-01-15')
|
|
},
|
|
{
|
|
symbol: 'MSFT',
|
|
dataSource: 'YAHOO',
|
|
quantity: 10,
|
|
unitPrice: 380,
|
|
date: new Date('2024-02-01')
|
|
},
|
|
{
|
|
symbol: 'VTI',
|
|
dataSource: 'YAHOO',
|
|
quantity: 25,
|
|
unitPrice: 230,
|
|
date: new Date('2024-03-10')
|
|
},
|
|
{
|
|
symbol: 'GOOGL',
|
|
dataSource: 'YAHOO',
|
|
quantity: 8,
|
|
unitPrice: 140,
|
|
date: new Date('2024-04-05')
|
|
},
|
|
{
|
|
symbol: 'AMZN',
|
|
dataSource: 'YAHOO',
|
|
quantity: 12,
|
|
unitPrice: 178,
|
|
date: new Date('2024-05-20')
|
|
},
|
|
{
|
|
symbol: 'AAPL',
|
|
dataSource: 'YAHOO',
|
|
quantity: 0,
|
|
unitPrice: 0.82,
|
|
date: new Date('2024-08-15'),
|
|
type: 'DIVIDEND'
|
|
},
|
|
{
|
|
symbol: 'MSFT',
|
|
dataSource: 'YAHOO',
|
|
quantity: 0,
|
|
unitPrice: 0.75,
|
|
date: new Date('2024-09-12'),
|
|
type: 'DIVIDEND'
|
|
}
|
|
];
|
|
|
|
for (const a of activities) {
|
|
const profile = await prisma.symbolProfile.findFirst({
|
|
where: {
|
|
symbol: a.symbol,
|
|
dataSource: a.dataSource as any
|
|
}
|
|
});
|
|
|
|
if (!profile) continue;
|
|
|
|
await prisma.order.create({
|
|
data: {
|
|
accountId: DEMO_ACCOUNT_ID,
|
|
accountUserId: DEMO_USER_ID,
|
|
currency: 'USD',
|
|
date: a.date,
|
|
fee: 0,
|
|
quantity: a.type === 'DIVIDEND' ? 0 : a.quantity,
|
|
symbolProfileId: profile.id,
|
|
type: a.type === 'DIVIDEND' ? 'DIVIDEND' : 'BUY',
|
|
unitPrice: a.unitPrice,
|
|
userId: DEMO_USER_ID
|
|
}
|
|
});
|
|
}
|
|
|
|
// Set DEMO_USER_ID and DEMO_ACCOUNT_ID in Property table
|
|
await prisma.property.upsert({
|
|
where: { key: 'DEMO_USER_ID' },
|
|
create: { key: 'DEMO_USER_ID', value: `"${DEMO_USER_ID}"` },
|
|
update: { value: `"${DEMO_USER_ID}"` }
|
|
});
|
|
|
|
await prisma.property.upsert({
|
|
where: { key: 'DEMO_ACCOUNT_ID' },
|
|
create: { key: 'DEMO_ACCOUNT_ID', value: `"${DEMO_ACCOUNT_ID}"` },
|
|
update: { value: `"${DEMO_ACCOUNT_ID}"` }
|
|
});
|
|
|
|
console.log('Demo user created successfully!');
|
|
console.log(` User ID: ${DEMO_USER_ID}`);
|
|
console.log(` Account ID: ${DEMO_ACCOUNT_ID}`);
|
|
console.log(' Evaluators can visit /demo to auto-login, then /ai-chat to use AI.');
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|
|
|