mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-25 15:14:38 +02:00
chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b97149ac12
commit
61d181fbc2
3148 changed files with 437 additions and 46640 deletions
24
apps-archived/uload/apps/web/src/lib/db/index.ts
Normal file
24
apps-archived/uload/apps/web/src/lib/db/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
|
||||
// Get connection string from environment
|
||||
const connectionString =
|
||||
process.env.DATABASE_URL || 'postgresql://uload:uload_dev_password_123@localhost:5432/uload_dev';
|
||||
|
||||
// Connection pool for queries
|
||||
export const client = postgres(connectionString, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
|
||||
// Drizzle instance with schema
|
||||
export const db = drizzle(client, { schema });
|
||||
|
||||
// Types for convenience
|
||||
export type DB = typeof db;
|
||||
export type TX = Parameters<Parameters<typeof db.transaction>[0]>[0];
|
||||
|
||||
// Export all schema tables and relations for easy access
|
||||
export * from './schema';
|
||||
413
apps-archived/uload/apps/web/src/lib/db/schema.ts
Normal file
413
apps-archived/uload/apps/web/src/lib/db/schema.ts
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
boolean,
|
||||
integer,
|
||||
timestamp,
|
||||
jsonb,
|
||||
index,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
// ============================================
|
||||
// Users Table
|
||||
// ============================================
|
||||
export const users = pgTable(
|
||||
'users',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
externalAuthId: text('external_auth_id').unique(), // For external auth provider
|
||||
email: text('email').unique().notNull(),
|
||||
username: text('username').unique().notNull(),
|
||||
name: text('name'),
|
||||
avatarUrl: text('avatar_url'),
|
||||
bio: text('bio'),
|
||||
location: text('location'),
|
||||
website: text('website'),
|
||||
github: text('github'),
|
||||
twitter: text('twitter'),
|
||||
linkedin: text('linkedin'),
|
||||
instagram: text('instagram'),
|
||||
publicProfile: boolean('public_profile').default(false),
|
||||
showClickStats: boolean('show_click_stats').default(true),
|
||||
emailNotifications: boolean('email_notifications').default(true),
|
||||
defaultExpiry: integer('default_expiry'),
|
||||
profileBackground: text('profile_background'),
|
||||
verified: boolean('verified').default(false),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
emailIdx: index('users_email_idx').on(table.email),
|
||||
usernameIdx: index('users_username_idx').on(table.username),
|
||||
externalAuthIdIdx: index('users_external_auth_id_idx').on(table.externalAuthId),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Accounts Table (Business/Team Accounts)
|
||||
// ============================================
|
||||
export const accounts = pgTable(
|
||||
'accounts',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
owner: uuid('owner')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
isActive: boolean('is_active').default(true),
|
||||
planType: text('plan_type', { enum: ['free', 'team', 'enterprise'] }).default('free'),
|
||||
settings: jsonb('settings'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
ownerIdx: index('accounts_owner_idx').on(table.owner),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Workspaces Table
|
||||
// ============================================
|
||||
export const workspaces = pgTable(
|
||||
'workspaces',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
slug: text('slug').unique().notNull(),
|
||||
type: text('type', { enum: ['personal', 'team'] }).notNull(),
|
||||
owner: uuid('owner')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
slugIdx: index('workspaces_slug_idx').on(table.slug),
|
||||
ownerIdx: index('workspaces_owner_idx').on(table.owner),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Links Table
|
||||
// ============================================
|
||||
export const links = pgTable(
|
||||
'links',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
shortCode: text('short_code').unique().notNull(),
|
||||
customCode: text('custom_code'),
|
||||
originalUrl: text('original_url').notNull(),
|
||||
title: text('title'),
|
||||
description: text('description'),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
isActive: boolean('is_active').default(true),
|
||||
password: text('password'), // hashed
|
||||
maxClicks: integer('max_clicks'),
|
||||
expiresAt: timestamp('expires_at'),
|
||||
clickCount: integer('click_count').default(0),
|
||||
qrCodeUrl: text('qr_code_url'), // File Storage URL
|
||||
tags: jsonb('tags').$type<string[]>(),
|
||||
utmSource: text('utm_source'),
|
||||
utmMedium: text('utm_medium'),
|
||||
utmCampaign: text('utm_campaign'),
|
||||
accountOwner: uuid('account_owner').references(() => accounts.id),
|
||||
workspaceId: uuid('workspace_id').references(() => workspaces.id),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('links_user_id_idx').on(table.userId),
|
||||
shortCodeIdx: index('links_short_code_idx').on(table.shortCode),
|
||||
workspaceIdIdx: index('links_workspace_id_idx').on(table.workspaceId),
|
||||
accountOwnerIdx: index('links_account_owner_idx').on(table.accountOwner),
|
||||
isActiveIdx: index('links_is_active_idx').on(table.isActive),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Clicks Table (Analytics)
|
||||
// ============================================
|
||||
export const clicks = pgTable(
|
||||
'clicks',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
linkId: uuid('link_id')
|
||||
.references(() => links.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
ipHash: text('ip_hash'),
|
||||
userAgent: text('user_agent'),
|
||||
referer: text('referer'),
|
||||
browser: text('browser'),
|
||||
deviceType: text('device_type'),
|
||||
os: text('os'),
|
||||
country: text('country'),
|
||||
city: text('city'),
|
||||
clickedAt: timestamp('clicked_at').defaultNow().notNull(),
|
||||
utmSource: text('utm_source'),
|
||||
utmMedium: text('utm_medium'),
|
||||
utmCampaign: text('utm_campaign'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
linkIdIdx: index('clicks_link_id_idx').on(table.linkId),
|
||||
clickedAtIdx: index('clicks_clicked_at_idx').on(table.clickedAt),
|
||||
countryIdx: index('clicks_country_idx').on(table.country),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Tags Table
|
||||
// ============================================
|
||||
export const tags = pgTable(
|
||||
'tags',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
slug: text('slug').notNull(),
|
||||
color: text('color'),
|
||||
icon: text('icon'),
|
||||
isPublic: boolean('is_public').default(false),
|
||||
usageCount: integer('usage_count').default(0),
|
||||
userId: uuid('user_id').references(() => users.id),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('tags_user_id_idx').on(table.userId),
|
||||
slugIdx: index('tags_slug_idx').on(table.slug),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Link-Tags Junction Table
|
||||
// ============================================
|
||||
export const linkTags = pgTable(
|
||||
'link_tags',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
linkId: uuid('link_id')
|
||||
.references(() => links.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
tagId: uuid('tag_id')
|
||||
.references(() => tags.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
linkIdIdx: index('link_tags_link_id_idx').on(table.linkId),
|
||||
tagIdIdx: index('link_tags_tag_id_idx').on(table.tagId),
|
||||
uniqueLinkTag: index('link_tags_unique_idx').on(table.linkId, table.tagId),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Notifications Table
|
||||
// ============================================
|
||||
export const notifications = pgTable(
|
||||
'notifications',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
type: text('type').notNull(),
|
||||
title: text('title').notNull(),
|
||||
message: text('message').notNull(),
|
||||
data: jsonb('data'),
|
||||
read: boolean('read').default(false),
|
||||
actionUrl: text('action_url'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('notifications_user_id_idx').on(table.userId),
|
||||
readIdx: index('notifications_read_idx').on(table.read),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Shared Access Table (Team Invitations)
|
||||
// ============================================
|
||||
export const sharedAccess = pgTable(
|
||||
'shared_access',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
owner: uuid('owner')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
userId: uuid('user_id').references(() => users.id),
|
||||
permissions: jsonb('permissions'),
|
||||
invitationStatus: text('invitation_status', {
|
||||
enum: ['pending', 'accepted', 'declined'],
|
||||
}).default('pending'),
|
||||
acceptedAt: timestamp('accepted_at'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
ownerIdx: index('shared_access_owner_idx').on(table.owner),
|
||||
userIdIdx: index('shared_access_user_id_idx').on(table.userId),
|
||||
statusIdx: index('shared_access_status_idx').on(table.invitationStatus),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Pending Invitations Table
|
||||
// ============================================
|
||||
export const pendingInvitations = pgTable(
|
||||
'pending_invitations',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
email: text('email').notNull(),
|
||||
token: text('token').unique().notNull(),
|
||||
owner: uuid('owner')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
expiresAt: timestamp('expires_at').notNull(),
|
||||
acceptedAt: timestamp('accepted_at'),
|
||||
acceptedBy: uuid('accepted_by').references(() => users.id),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
emailIdx: index('pending_invitations_email_idx').on(table.email),
|
||||
tokenIdx: index('pending_invitations_token_idx').on(table.token),
|
||||
ownerIdx: index('pending_invitations_owner_idx').on(table.owner),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Feature Requests Table
|
||||
// ============================================
|
||||
export const featureRequests = pgTable(
|
||||
'feature_requests',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
title: text('title').notNull(),
|
||||
description: text('description').notNull(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
status: text('status', {
|
||||
enum: ['pending', 'reviewing', 'planned', 'completed', 'rejected'],
|
||||
}).default('pending'),
|
||||
voteCount: integer('vote_count').default(0),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('feature_requests_user_id_idx').on(table.userId),
|
||||
statusIdx: index('feature_requests_status_idx').on(table.status),
|
||||
voteCountIdx: index('feature_requests_vote_count_idx').on(table.voteCount),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Feature Votes Table
|
||||
// ============================================
|
||||
export const featureVotes = pgTable(
|
||||
'feature_votes',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
featureRequestId: uuid('feature_request_id')
|
||||
.references(() => featureRequests.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
featureRequestIdIdx: index('feature_votes_feature_request_id_idx').on(table.featureRequestId),
|
||||
userIdIdx: index('feature_votes_user_id_idx').on(table.userId),
|
||||
uniqueVote: index('feature_votes_unique_idx').on(table.featureRequestId, table.userId),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Folders Table (minimal usage, keep for future)
|
||||
// ============================================
|
||||
export const folders = pgTable(
|
||||
'folders',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('folders_user_id_idx').on(table.userId),
|
||||
})
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Relations (for Drizzle Relational Queries)
|
||||
// ============================================
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
links: many(links),
|
||||
tags: many(tags),
|
||||
notifications: many(notifications),
|
||||
ownedAccounts: many(accounts),
|
||||
ownedWorkspaces: many(workspaces),
|
||||
featureRequests: many(featureRequests),
|
||||
featureVotes: many(featureVotes),
|
||||
folders: many(folders),
|
||||
}));
|
||||
|
||||
export const linksRelations = relations(links, ({ one, many }) => ({
|
||||
user: one(users, { fields: [links.userId], references: [users.id] }),
|
||||
account: one(accounts, { fields: [links.accountOwner], references: [accounts.id] }),
|
||||
workspace: one(workspaces, { fields: [links.workspaceId], references: [workspaces.id] }),
|
||||
clicks: many(clicks),
|
||||
linkTags: many(linkTags),
|
||||
}));
|
||||
|
||||
export const clicksRelations = relations(clicks, ({ one }) => ({
|
||||
link: one(links, { fields: [clicks.linkId], references: [links.id] }),
|
||||
}));
|
||||
|
||||
export const tagsRelations = relations(tags, ({ one, many }) => ({
|
||||
user: one(users, { fields: [tags.userId], references: [users.id] }),
|
||||
linkTags: many(linkTags),
|
||||
}));
|
||||
|
||||
export const linkTagsRelations = relations(linkTags, ({ one }) => ({
|
||||
link: one(links, { fields: [linkTags.linkId], references: [links.id] }),
|
||||
tag: one(tags, { fields: [linkTags.tagId], references: [tags.id] }),
|
||||
}));
|
||||
|
||||
export const accountsRelations = relations(accounts, ({ one, many }) => ({
|
||||
owner: one(users, { fields: [accounts.owner], references: [users.id] }),
|
||||
links: many(links),
|
||||
}));
|
||||
|
||||
export const workspacesRelations = relations(workspaces, ({ one, many }) => ({
|
||||
owner: one(users, { fields: [workspaces.owner], references: [users.id] }),
|
||||
links: many(links),
|
||||
}));
|
||||
|
||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
user: one(users, { fields: [notifications.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const featureRequestsRelations = relations(featureRequests, ({ one, many }) => ({
|
||||
user: one(users, { fields: [featureRequests.userId], references: [users.id] }),
|
||||
votes: many(featureVotes),
|
||||
}));
|
||||
|
||||
export const featureVotesRelations = relations(featureVotes, ({ one }) => ({
|
||||
featureRequest: one(featureRequests, {
|
||||
fields: [featureVotes.featureRequestId],
|
||||
references: [featureRequests.id],
|
||||
}),
|
||||
user: one(users, { fields: [featureVotes.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const foldersRelations = relations(folders, ({ one }) => ({
|
||||
user: one(users, { fields: [folders.userId], references: [users.id] }),
|
||||
}));
|
||||
Loading…
Add table
Add a link
Reference in a new issue