新增构建OpenSSL镜像相关文件
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
FROM node:14.18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN NODE_ENV=production yarn build
|
||||
|
||||
ENV PORT=8080
|
||||
EXPOSE 8080
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
CMD yarn start
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "ts-node-dev --files src/index.ts",
|
||||
"start": "node dist/index.js",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node14": "^1.0.1",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/express-session": "^1.17.3",
|
||||
"@types/flat": "^5.0.1",
|
||||
"@types/node": "^15.14.0",
|
||||
"@types/react": "^17.0.13",
|
||||
"@types/react-datepicker": "^4.1.1",
|
||||
"@types/react-router": "^5.1.15",
|
||||
"@types/styled-components": "^5.1.10",
|
||||
"@types/styled-system": "^5.1.11",
|
||||
"@types/validator": "^13.6.0",
|
||||
"nodemon": "^2.0.15",
|
||||
"ts-node": "^10.0.0",
|
||||
"ts-node-dev": "^1.1.8",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adminjs/express": "^4.0.0",
|
||||
"@adminjs/typeorm": "^2.0.0",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"adminjs": "5.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"express": "^4.17.3",
|
||||
"express-formidable": "^1.2.0",
|
||||
"express-session": "^1.17.2",
|
||||
"pg": "^8.6.0",
|
||||
"read-yaml-file": "^2.1.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"typeorm": "^0.2.34"
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true
|
||||
},
|
||||
"files": [
|
||||
"./src/index.ts",
|
||||
"./src/db.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/node_modules"
|
||||
],
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
438
examples/omnivore/official-src/omnivore-main/pkg/admin/src/db.ts
Normal file
438
examples/omnivore/official-src/omnivore-main/pkg/admin/src/db.ts
Normal file
@@ -0,0 +1,438 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
createConnection,
|
||||
Connection,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
} from 'typeorm'
|
||||
import AdminJs from 'adminjs'
|
||||
import { Database, Resource } from '@adminjs/typeorm'
|
||||
|
||||
export const registerDatabase = async (secrets: any): Promise<Connection> => {
|
||||
AdminJs.registerAdapter({ Database, Resource })
|
||||
|
||||
let host = 'localhost'
|
||||
if (process.env.K_SERVICE) {
|
||||
console.log(
|
||||
'connecting to database via Cloud Run connection',
|
||||
process.env.CLOUD_SQL_CONNECTION_NAME
|
||||
)
|
||||
const dbSocketPath = process.env.DB_SOCKET_PATH || '/cloudsql'
|
||||
host = `${dbSocketPath}/${process.env.CLOUD_SQL_CONNECTION_NAME}`
|
||||
}
|
||||
|
||||
console.log('connecting to database:', {
|
||||
type: 'postgres',
|
||||
host: host,
|
||||
schema: 'omnivore',
|
||||
database: secrets.DB_DATABASE,
|
||||
})
|
||||
|
||||
const connection = await createConnection({
|
||||
type: 'postgres',
|
||||
host: host,
|
||||
schema: 'omnivore',
|
||||
username: secrets.DB_USER,
|
||||
password: secrets.DB_PASS,
|
||||
database: secrets.DB_DATABASE,
|
||||
entities: [
|
||||
AdminUser,
|
||||
User,
|
||||
UserProfile,
|
||||
UserArticle,
|
||||
ReceivedEmail,
|
||||
ContentDisplayReport,
|
||||
Group,
|
||||
Integration,
|
||||
Subscription,
|
||||
LibraryItem,
|
||||
UploadFile,
|
||||
Recommendation,
|
||||
GroupMembership,
|
||||
],
|
||||
})
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
export enum StatusType {
|
||||
Active = 'ACTIVE',
|
||||
Pending = 'PENDING',
|
||||
}
|
||||
|
||||
export enum AuthProvider {
|
||||
Apple = 'APPLE',
|
||||
Google = 'GOOGLE',
|
||||
Email = 'EMAIL',
|
||||
}
|
||||
|
||||
@Entity({ name: 'admin_user' })
|
||||
export class AdminUser extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
public email!: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
public password!: string
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@Column('text')
|
||||
name!: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
public email!: string
|
||||
|
||||
@Column({ type: 'timestamp' })
|
||||
public created_at!: Date
|
||||
|
||||
@Column({ type: 'timestamp' })
|
||||
public updated_at!: Date
|
||||
|
||||
@Column({ type: 'enum', enum: StatusType })
|
||||
status!: StatusType
|
||||
|
||||
@OneToMany(() => UserArticle, (ua) => ua.user)
|
||||
articles!: UserArticle[]
|
||||
|
||||
@Column({ type: 'text' })
|
||||
source_user_id!: string
|
||||
|
||||
@Column({ type: 'enum', enum: AuthProvider })
|
||||
source!: AuthProvider
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class UserProfile extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
public username!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
}
|
||||
|
||||
@Entity({ name: 'user_articles' })
|
||||
export class UserArticle extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column({ type: 'text', name: 'article_id' })
|
||||
articleId!: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
slug!: string
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'saved_at' })
|
||||
savedAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'content_display_report' })
|
||||
export class ContentDisplayReport extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column({ type: 'text', name: 'original_url' })
|
||||
originalUrl!: string
|
||||
|
||||
@Column({ type: 'text', name: 'report_comment' })
|
||||
reportComment!: string
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'received_emails' })
|
||||
export class ReceivedEmail extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column('text')
|
||||
from!: string
|
||||
|
||||
@Column('text')
|
||||
to!: string
|
||||
|
||||
@Column('text')
|
||||
subject!: string
|
||||
|
||||
@Column('text')
|
||||
text!: string
|
||||
|
||||
@Column('text')
|
||||
html!: string
|
||||
|
||||
@Column('text')
|
||||
type!: 'article' | 'non-article'
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Group extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@Column('text')
|
||||
name!: string
|
||||
|
||||
@OneToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by_id' })
|
||||
createdBy!: User
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
description?: string | null
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
topics?: string | null
|
||||
|
||||
@Column('boolean', { default: false, name: 'only_admin_can_post' })
|
||||
onlyAdminCanPost!: boolean
|
||||
|
||||
@Column('boolean', { default: false, name: 'only_admin_can_see_members' })
|
||||
onlyAdminCanSeeMembers!: boolean
|
||||
}
|
||||
|
||||
@Entity({ name: 'integrations' })
|
||||
export class Integration extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column('varchar', { length: 40 })
|
||||
name!: string
|
||||
|
||||
@Column('varchar', { length: 255 })
|
||||
token!: string
|
||||
|
||||
@Column('boolean', { default: true })
|
||||
enabled!: boolean
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column({ name: 'synced_at', type: 'timestamp', nullable: true })
|
||||
syncedAt?: Date | null
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
Active = 'ACTIVE',
|
||||
Deleted = 'DELETED',
|
||||
Unsubscribed = 'UNSUBSCRIBED',
|
||||
}
|
||||
|
||||
enum SubscriptionType {
|
||||
Newsletter = 'NEWSLETTER',
|
||||
Rss = 'RSS',
|
||||
}
|
||||
|
||||
@Entity({ name: 'subscriptions' })
|
||||
export class Subscription extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column('text')
|
||||
name!: string
|
||||
|
||||
@Column('enum', {
|
||||
enum: SubscriptionStatus,
|
||||
default: SubscriptionStatus.Active,
|
||||
})
|
||||
status!: SubscriptionStatus
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
url?: string
|
||||
|
||||
@Column('enum', {
|
||||
enum: SubscriptionType,
|
||||
})
|
||||
type!: SubscriptionType
|
||||
|
||||
@Column('integer', { default: 0 })
|
||||
count!: number
|
||||
|
||||
@Column({ type: 'timestamp', name: 'last_fetched_at', nullable: true })
|
||||
lastFetchedAt?: Date | null
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'library_item' })
|
||||
export class LibraryItem extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column({ type: 'text', name: 'original_url' })
|
||||
originalUrl!: string
|
||||
|
||||
@Column('text')
|
||||
slug!: string
|
||||
|
||||
@Column('text')
|
||||
title!: string
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
author?: string | null
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
subscription?: string | null
|
||||
|
||||
@Column('text', { array: true, nullable: true })
|
||||
recommender_names?: string[] | null
|
||||
|
||||
@Column('text', { array: true, nullable: true })
|
||||
label_names?: string[] | null
|
||||
|
||||
@OneToOne(() => UploadFile, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'upload_file_id' })
|
||||
uploadFile?: UploadFile
|
||||
|
||||
@Column({ type: 'timestamp', name: 'saved_at' })
|
||||
savedAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'deleted_at' })
|
||||
deletedAt?: Date | null
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'upload_files' })
|
||||
export class UploadFile extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@Column('text')
|
||||
url!: string
|
||||
|
||||
@Column('text')
|
||||
fileName!: string
|
||||
|
||||
@Column('text')
|
||||
contentType!: string
|
||||
|
||||
@Column('text')
|
||||
status!: string
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'recommendation' })
|
||||
export class Recommendation extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'recommender_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
recommender!: User
|
||||
|
||||
@JoinColumn({ name: 'library_item_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
libraryItem!: LibraryItem
|
||||
|
||||
@JoinColumn({ name: 'group_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
group!: Group
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
note?: string | null
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
}
|
||||
|
||||
@Entity({ name: 'group_membership' })
|
||||
export class GroupMembership extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
@ManyToOne(() => User, (user) => user.articles, { eager: true })
|
||||
user!: User
|
||||
|
||||
@JoinColumn({ name: 'group_id' })
|
||||
group!: Group
|
||||
|
||||
@Column({ type: 'timestamp', name: 'created_at' })
|
||||
createdAt!: Date
|
||||
|
||||
@Column({ type: 'timestamp', name: 'updated_at' })
|
||||
updatedAt!: Date
|
||||
|
||||
@Column('boolean', { default: false })
|
||||
is_admin!: boolean
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import express from 'express'
|
||||
import AdminJs from 'adminjs'
|
||||
import AdminJsExpress from '@adminjs/express'
|
||||
import {
|
||||
registerDatabase,
|
||||
AdminUser,
|
||||
User,
|
||||
UserArticle,
|
||||
UserProfile,
|
||||
ReceivedEmail,
|
||||
Group,
|
||||
ContentDisplayReport,
|
||||
Subscription,
|
||||
Integration,
|
||||
LibraryItem,
|
||||
Recommendation,
|
||||
GroupMembership,
|
||||
} from './db'
|
||||
import { compare, hashSync } from 'bcryptjs'
|
||||
const readYamlFile = require('read-yaml-file')
|
||||
|
||||
const app = express()
|
||||
const port = process.env.PORT || '8000'
|
||||
const ADMIN_USER_EMAIL =
|
||||
process.env.ADMIN_USER_EMAIL || 'admin-user@omnivore.app'
|
||||
|
||||
;(async () => {
|
||||
const secrets = await readYamlFile(process.env.SECRETS_FILE)
|
||||
const db = await registerDatabase(secrets)
|
||||
|
||||
const adminBro = new AdminJs({
|
||||
databases: [db],
|
||||
rootPath: '/admin',
|
||||
resources: [
|
||||
{
|
||||
resource: User,
|
||||
options: {
|
||||
parent: { name: 'Users' },
|
||||
},
|
||||
},
|
||||
{ resource: UserProfile, options: { parent: { name: 'Users' } } },
|
||||
{ resource: UserArticle, options: { parent: { name: 'Users' } } },
|
||||
{ resource: ReceivedEmail, options: { parent: { name: 'Users' } } },
|
||||
{ resource: Group, options: { parent: { name: 'Users' } } },
|
||||
{ resource: Subscription, options: { parent: { name: 'Users' } } },
|
||||
{ resource: Integration, options: { parent: { name: 'Users' } } },
|
||||
{ resource: LibraryItem, options: { parent: { name: 'Users' } } },
|
||||
{
|
||||
resource: ContentDisplayReport,
|
||||
},
|
||||
{ resource: Recommendation, options: { parent: { name: 'Users' } } },
|
||||
{ resource: GroupMembership, options: { parent: { name: 'Users' } } },
|
||||
],
|
||||
})
|
||||
|
||||
const router = AdminJsExpress.buildAuthenticatedRouter(adminBro, {
|
||||
authenticate: async (email, password) => {
|
||||
const user = await AdminUser.findOne({ email })
|
||||
console.log('looked up user: ', user)
|
||||
if (user) {
|
||||
const matched = await compare(password, user.password)
|
||||
console.log(' -- failed match')
|
||||
return matched ? user : false
|
||||
} else if (email === ADMIN_USER_EMAIL) {
|
||||
console.log('user is admin user, creating account')
|
||||
// If no admin user has been created yet, create one
|
||||
// from the environment variables. This is only done
|
||||
// once, and then the admin user should create a user
|
||||
// for each user.
|
||||
if (!secrets.ADMIN_USER_PASSWORD) {
|
||||
throw new Error('ADMIN_USER_PASSWORD is not set')
|
||||
}
|
||||
return AdminUser.create({
|
||||
email: ADMIN_USER_EMAIL,
|
||||
password: hashSync(secrets.ADMIN_USER_PASSWORD, 10),
|
||||
})
|
||||
}
|
||||
return false
|
||||
},
|
||||
cookiePassword:
|
||||
secrets.ADMIN_USER_PASSWORD ||
|
||||
'some-secret-password-used-to-secure-cookie',
|
||||
})
|
||||
|
||||
app.use(adminBro.options.rootPath, router)
|
||||
|
||||
app.listen(port, () => {
|
||||
return console.log(`Server is listening on ${port}`)
|
||||
})
|
||||
})()
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@tsconfig/node14/tsconfig.json",
|
||||
"ts-node": {
|
||||
"files": true
|
||||
},
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["dom"]
|
||||
},
|
||||
"include": ["src", "test"],
|
||||
"exclude": ["./src/generated"]
|
||||
}
|
||||
5255
examples/omnivore/official-src/omnivore-main/pkg/admin/yarn.lock
Normal file
5255
examples/omnivore/official-src/omnivore-main/pkg/admin/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user