mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
fix typo
This commit is contained in:
parent
65bb4aa85f
commit
3f572ea89a
36 changed files with 562 additions and 1286 deletions
|
|
@ -1,235 +0,0 @@
|
|||
// Define the type of the body for the Search request
|
||||
import { PubsubClient } from '../pubsub'
|
||||
import { PickTuple } from '../util'
|
||||
import {
|
||||
DateFilter,
|
||||
FieldFilter,
|
||||
HasFilter,
|
||||
InFilter,
|
||||
LabelFilter,
|
||||
NoFilter,
|
||||
ReadFilter,
|
||||
SortParams,
|
||||
} from '../utils/search'
|
||||
|
||||
// Complete definition of the Search response
|
||||
export interface ShardsResponse {
|
||||
total: number
|
||||
successful: number
|
||||
failed: number
|
||||
skipped: number
|
||||
}
|
||||
|
||||
export interface Explanation {
|
||||
value: number
|
||||
description: string
|
||||
details: Explanation[]
|
||||
}
|
||||
|
||||
export interface SearchResponse<T> {
|
||||
took: number
|
||||
timed_out: boolean
|
||||
_scroll_id?: string
|
||||
_shards: ShardsResponse
|
||||
hits: {
|
||||
total: {
|
||||
value: number
|
||||
}
|
||||
max_score: number
|
||||
hits: Array<{
|
||||
_index: string
|
||||
_type: string
|
||||
_id: string
|
||||
_score: number
|
||||
_source: T
|
||||
_version?: number
|
||||
_explanation?: Explanation
|
||||
fields?: never
|
||||
highlight?: never
|
||||
inner_hits?: unknown
|
||||
matched_queries?: string[]
|
||||
sort?: string[]
|
||||
}>
|
||||
}
|
||||
aggregations?: never
|
||||
}
|
||||
|
||||
export enum PageType {
|
||||
Article = 'ARTICLE',
|
||||
Book = 'BOOK',
|
||||
File = 'FILE',
|
||||
Profile = 'PROFILE',
|
||||
Unknown = 'UNKNOWN',
|
||||
Website = 'WEBSITE',
|
||||
Highlights = 'HIGHLIGHTS',
|
||||
Tweet = 'TWEET',
|
||||
Video = 'VIDEO',
|
||||
Image = 'IMAGE',
|
||||
}
|
||||
|
||||
export enum ArticleSavingRequestStatus {
|
||||
Failed = 'FAILED',
|
||||
Processing = 'PROCESSING',
|
||||
Succeeded = 'SUCCEEDED',
|
||||
Deleted = 'DELETED',
|
||||
|
||||
Archived = 'ARCHIVED',
|
||||
}
|
||||
|
||||
export enum HighlightType {
|
||||
Highlight = 'HIGHLIGHT',
|
||||
Redaction = 'REDACTION', // allowing people to remove text from the page
|
||||
Note = 'NOTE', // allowing people to add a note at the document level
|
||||
}
|
||||
|
||||
export interface Label {
|
||||
id: string
|
||||
name: string
|
||||
color: string
|
||||
description?: string | null
|
||||
createdAt?: Date
|
||||
}
|
||||
|
||||
export interface Highlight {
|
||||
id: string
|
||||
shortId: string
|
||||
patch?: string | null
|
||||
quote?: string | null
|
||||
userId: string
|
||||
createdAt: Date
|
||||
prefix?: string | null
|
||||
suffix?: string | null
|
||||
annotation?: string | null
|
||||
sharedAt?: Date | null
|
||||
updatedAt: Date
|
||||
labels?: Label[]
|
||||
highlightPositionPercent?: number | null
|
||||
highlightPositionAnchorIndex?: number | null
|
||||
type: HighlightType
|
||||
html?: string | null
|
||||
color?: string | null
|
||||
}
|
||||
|
||||
export interface RecommendingUser {
|
||||
userId: string
|
||||
name: string
|
||||
username: string
|
||||
profileImageURL?: string | null
|
||||
}
|
||||
|
||||
export interface Recommendation {
|
||||
id: string
|
||||
name: string
|
||||
note?: string | null
|
||||
user: RecommendingUser
|
||||
recommendedAt: Date
|
||||
}
|
||||
|
||||
export interface Page {
|
||||
id: string
|
||||
userId: string
|
||||
title: string
|
||||
author?: string
|
||||
description?: string
|
||||
content: string
|
||||
url: string
|
||||
hash: string
|
||||
uploadFileId?: string | null
|
||||
image?: string
|
||||
pageType: PageType
|
||||
originalHtml?: string | null
|
||||
slug: string
|
||||
labels?: Label[]
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressPercent: number
|
||||
readingProgressAnchorIndex: number
|
||||
createdAt: Date
|
||||
updatedAt?: Date
|
||||
publishedAt?: Date
|
||||
savedAt: Date
|
||||
sharedAt?: Date
|
||||
archivedAt?: Date | null
|
||||
siteName?: string
|
||||
_id?: string
|
||||
siteIcon?: string
|
||||
highlights?: Highlight[]
|
||||
subscription?: string
|
||||
unsubMailTo?: string
|
||||
unsubHttpUrl?: string
|
||||
state: ArticleSavingRequestStatus
|
||||
taskName?: string
|
||||
language?: string
|
||||
readAt?: Date
|
||||
listenedAt?: Date
|
||||
wordsCount?: number
|
||||
recommendations?: Recommendation[]
|
||||
rssFeedUrl?: string
|
||||
}
|
||||
|
||||
export interface SearchItem {
|
||||
annotation?: string | null
|
||||
author?: string | null
|
||||
createdAt: Date
|
||||
description?: string | null
|
||||
id: string
|
||||
image?: string | null
|
||||
pageId?: string
|
||||
pageType: PageType
|
||||
publishedAt?: Date
|
||||
quote?: string | null
|
||||
shortId?: string | null
|
||||
slug: string
|
||||
title: string
|
||||
uploadFileId?: string | null
|
||||
url: string
|
||||
archivedAt?: Date | null
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressPercent: number
|
||||
readingProgressAnchorIndex: number
|
||||
userId: string
|
||||
state?: ArticleSavingRequestStatus
|
||||
language?: string
|
||||
readAt?: Date
|
||||
savedAt: Date
|
||||
updatedAt?: Date
|
||||
labels?: Label[]
|
||||
highlights?: Highlight[]
|
||||
wordsCount?: number
|
||||
siteName?: string
|
||||
siteIcon?: string
|
||||
recommendations?: Recommendation[]
|
||||
content?: string
|
||||
}
|
||||
|
||||
const keys = ['_id', 'url', 'slug', 'userId', 'uploadFileId', 'state'] as const
|
||||
|
||||
export type ParamSet = PickTuple<Page, typeof keys>
|
||||
|
||||
export interface PageContext {
|
||||
pubsub: PubsubClient
|
||||
refresh?: boolean
|
||||
uid: string
|
||||
shouldPublish?: boolean
|
||||
}
|
||||
|
||||
export interface PageSearchArgs {
|
||||
from?: number
|
||||
size?: number
|
||||
sort?: SortParams
|
||||
query?: string
|
||||
inFilter?: InFilter
|
||||
readFilter?: ReadFilter
|
||||
typeFilter?: PageType
|
||||
labelFilters?: LabelFilter[]
|
||||
hasFilters?: HasFilter[]
|
||||
dateFilters?: DateFilter[]
|
||||
termFilters?: FieldFilter[]
|
||||
matchFilters?: FieldFilter[]
|
||||
includePending?: boolean | null
|
||||
includeDeleted?: boolean
|
||||
ids?: string[]
|
||||
recommendedBy?: string
|
||||
includeContent?: boolean
|
||||
noFilters?: NoFilter[]
|
||||
siteName?: string
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { entityManager } from '.'
|
||||
import { Group } from '../entity/groups/group'
|
||||
|
||||
export const groupRepository = entityManager.getRepository(Group)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import * as httpContext from 'express-http-context2'
|
||||
import { EntityManager } from 'typeorm'
|
||||
import { EntityManager, EntityTarget } from 'typeorm'
|
||||
import { appDataSource } from '../data_source'
|
||||
import { Claims } from '../resolvers/types'
|
||||
|
||||
|
|
@ -33,4 +33,8 @@ export const authTrx = async <T>(
|
|||
})
|
||||
}
|
||||
|
||||
export const getRepository = <T>(entity: EntityTarget<T>) => {
|
||||
return entityManager.getRepository(entity)
|
||||
}
|
||||
|
||||
export const entityManager = appDataSource.manager
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
import { Profile } from '../entity/profile'
|
||||
import { entityManager } from '.'
|
||||
|
||||
export const profileRepository = entityManager.getRepository(Profile)
|
||||
|
|
@ -13,21 +13,21 @@ import {
|
|||
RevokeApiKeyErrorCode,
|
||||
RevokeApiKeySuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { getRepository } from '../../repository'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { generateApiKey, hashApiKey } from '../../utils/auth'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
|
||||
export const apiKeysResolver = authorized<ApiKeysSuccess, ApiKeysError>(
|
||||
async (_, __, { log, authTrx }) => {
|
||||
async (_, __, { log, uid }) => {
|
||||
try {
|
||||
const apiKeys = await authTrx(async (tx) => {
|
||||
return tx.getRepository(ApiKey).find({
|
||||
select: ['id', 'name', 'scopes', 'expiresAt', 'createdAt', 'usedAt'],
|
||||
order: {
|
||||
usedAt: { direction: 'DESC', nulls: 'last' },
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
})
|
||||
const apiKeys = await getRepository(ApiKey).find({
|
||||
select: ['id', 'name', 'scopes', 'expiresAt', 'createdAt', 'usedAt'],
|
||||
where: { user: { id: uid } },
|
||||
order: {
|
||||
usedAt: { direction: 'DESC', nulls: 'last' },
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
@ -47,17 +47,15 @@ export const generateApiKeyResolver = authorized<
|
|||
GenerateApiKeySuccess,
|
||||
GenerateApiKeyError,
|
||||
MutationGenerateApiKeyArgs
|
||||
>(async (_, { input: { name, expiresAt } }, { authTrx, log, uid }) => {
|
||||
>(async (_, { input: { name, expiresAt } }, { log, uid }) => {
|
||||
try {
|
||||
const exp = new Date(expiresAt)
|
||||
const originalKey = generateApiKey()
|
||||
const apiKeyCreated = await authTrx(async (tx) => {
|
||||
return tx.getRepository(ApiKey).save({
|
||||
user: { id: uid },
|
||||
name,
|
||||
key: hashApiKey(originalKey),
|
||||
expiresAt: exp,
|
||||
})
|
||||
const apiKeyCreated = await getRepository(ApiKey).save({
|
||||
user: { id: uid },
|
||||
name,
|
||||
key: hashApiKey(originalKey),
|
||||
expiresAt: exp,
|
||||
})
|
||||
|
||||
analytics.track({
|
||||
|
|
@ -89,22 +87,17 @@ export const revokeApiKeyResolver = authorized<
|
|||
MutationRevokeApiKeyArgs
|
||||
>(async (_, { id }, { claims: { uid }, log, authTrx }) => {
|
||||
try {
|
||||
const deletedApiKey = await authTrx(async (tx) => {
|
||||
const apiRepo = tx.getRepository(ApiKey)
|
||||
const apiKey = await apiRepo.findOneBy({ id })
|
||||
if (!apiKey) {
|
||||
return null
|
||||
}
|
||||
const apiRepo = getRepository(ApiKey)
|
||||
|
||||
return apiRepo.remove(apiKey)
|
||||
})
|
||||
|
||||
if (!deletedApiKey) {
|
||||
const apiKey = await apiRepo.findOneBy({ id, user: { id: uid } })
|
||||
if (!apiKey) {
|
||||
return {
|
||||
errorCodes: [RevokeApiKeyErrorCode.NotFound],
|
||||
}
|
||||
}
|
||||
|
||||
const deletedApiKey = await apiRepo.remove(apiKey)
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'api_key_revoked',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { getPageByParam } from '../elastic/pages'
|
||||
import { Subscription } from '../entity/subscription'
|
||||
import { Article, PageType, SearchItem } from '../generated/graphql'
|
||||
import { findUploadFileById } from '../services/upload_file'
|
||||
|
|
@ -109,7 +108,7 @@ import {
|
|||
} from './index'
|
||||
import { markEmailAsItemResolver, recentEmailsResolver } from './recent_emails'
|
||||
import { recentSearchesResolver } from './recent_searches'
|
||||
import { Claims, WithDataSourcesContext } from './types'
|
||||
import { WithDataSourcesContext } from './types'
|
||||
import { updateEmailResolver } from './user'
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
|
@ -387,36 +386,6 @@ export const functionResolvers = {
|
|||
async originalArticleUrl(article: { url: string }) {
|
||||
return article.url
|
||||
},
|
||||
async savedByViewer(
|
||||
article: { id: string; savedByViewer?: boolean },
|
||||
__: unknown,
|
||||
ctx: WithDataSourcesContext & { claims: Claims }
|
||||
) {
|
||||
if (article.savedByViewer) {
|
||||
return article.savedByViewer
|
||||
}
|
||||
if (!ctx.claims?.uid) return undefined
|
||||
const page = await getPageByParam({
|
||||
userId: ctx.claims.uid,
|
||||
_id: article.id,
|
||||
})
|
||||
return !!page
|
||||
},
|
||||
async postedByViewer(
|
||||
article: { id: string; postedByViewer?: boolean },
|
||||
__: unknown,
|
||||
ctx: WithDataSourcesContext & { claims: Claims }
|
||||
) {
|
||||
if (article.postedByViewer) {
|
||||
return article.postedByViewer
|
||||
}
|
||||
if (!ctx.claims?.uid) return false
|
||||
const page = await getPageByParam({
|
||||
userId: ctx.claims.uid,
|
||||
_id: article.id,
|
||||
})
|
||||
return !!page?.sharedAt
|
||||
},
|
||||
hasContent(article: {
|
||||
content: string | null
|
||||
originalHtml: string | null
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
NewsletterEmailsErrorCode,
|
||||
NewsletterEmailsSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { getRepository } from '../../repository'
|
||||
import {
|
||||
createNewsletterEmail,
|
||||
deleteNewsletterEmail,
|
||||
|
|
@ -57,7 +58,6 @@ export const newsletterEmailsResolver = authorized<
|
|||
NewsletterEmailsSuccess,
|
||||
NewsletterEmailsError
|
||||
>(async (_parent, _args, { uid, log }) => {
|
||||
|
||||
try {
|
||||
const newsletterEmails = await getNewsletterEmails(uid)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
RecentEmailsErrorCode,
|
||||
RecentEmailsSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { getRepository } from '../../repository'
|
||||
import { updateReceivedEmail } from '../../services/received_emails'
|
||||
import { saveNewsletter } from '../../services/save_newsletter_email'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { In } from 'typeorm'
|
||||
import { getPageByParam } from '../../elastic/pages'
|
||||
import { Group } from '../../entity/groups/group'
|
||||
import { User } from '../../entity/user'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
CreateGroupError,
|
||||
|
|
@ -28,6 +26,7 @@ import {
|
|||
RecommendHighlightsSuccess,
|
||||
RecommendSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { getRepository } from '../../repository'
|
||||
import { userRepository } from '../../repository/user'
|
||||
import {
|
||||
createGroup,
|
||||
|
|
@ -38,6 +37,7 @@ import {
|
|||
joinGroup,
|
||||
leaveGroup,
|
||||
} from '../../services/groups'
|
||||
import { findLibraryItemById } from '../../services/library_item'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { enqueueRecommendation } from '../../utils/createTask'
|
||||
import { authorized, userDataToUser } from '../../utils/helpers'
|
||||
|
|
@ -167,7 +167,7 @@ export const recommendResolver = authorized<
|
|||
RecommendSuccess,
|
||||
RecommendError,
|
||||
MutationRecommendArgs
|
||||
>(async (_, { input }, { claims: { uid }, log, signToken }) => {
|
||||
>(async (_, { input }, { uid, log, signToken }) => {
|
||||
log.info('Recommend', {
|
||||
input,
|
||||
labels: {
|
||||
|
|
@ -178,18 +178,8 @@ export const recommendResolver = authorized<
|
|||
})
|
||||
|
||||
try {
|
||||
const user = await userRepository.findOne({
|
||||
where: { id: uid },
|
||||
relations: ['profile'],
|
||||
})
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [RecommendErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const page = await getPageByParam({ _id: input.pageId, userId: uid })
|
||||
if (!page) {
|
||||
const item = await findLibraryItemById(input.pageId, uid)
|
||||
if (!item) {
|
||||
return {
|
||||
errorCodes: [RecommendErrorCode.NotFound],
|
||||
}
|
||||
|
|
@ -205,7 +195,7 @@ export const recommendResolver = authorized<
|
|||
|
||||
// only recommend highlights created by the user
|
||||
const recommendedHighlightIds = input.recommendedWithHighlights
|
||||
? page.highlights?.filter((h) => h.userId === uid)?.map((h) => h.id)
|
||||
? item.highlights?.filter((h) => h.user.id === uid)?.map((h) => h.id)
|
||||
: undefined
|
||||
|
||||
const exp = Math.floor(Date.now() / 1000) + 60 * 60 * 24 // 1 day
|
||||
|
|
@ -216,18 +206,13 @@ export const recommendResolver = authorized<
|
|||
group.members.map((member) =>
|
||||
enqueueRecommendation(
|
||||
member.user.id,
|
||||
page.id,
|
||||
item.id,
|
||||
{
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
note: input.note ?? null,
|
||||
user: {
|
||||
userId: user.id,
|
||||
name: user.name,
|
||||
username: user.profile.username,
|
||||
profileImageURL: user.profile.pictureUrl,
|
||||
},
|
||||
recommendedAt: new Date(),
|
||||
recommender: item.user,
|
||||
createdAt: new Date(),
|
||||
libraryItem: item,
|
||||
},
|
||||
auth,
|
||||
recommendedHighlightIds
|
||||
|
|
@ -318,7 +303,7 @@ export const recommendHighlightsResolver = authorized<
|
|||
RecommendHighlightsSuccess,
|
||||
RecommendHighlightsError,
|
||||
MutationRecommendHighlightsArgs
|
||||
>(async (_, { input }, { claims: { uid }, log, signToken }) => {
|
||||
>(async (_, { input }, { uid, log, signToken }) => {
|
||||
log.info('Recommend highlights', {
|
||||
input,
|
||||
labels: {
|
||||
|
|
@ -349,8 +334,8 @@ export const recommendHighlightsResolver = authorized<
|
|||
}
|
||||
}
|
||||
|
||||
const page = await getPageByParam({ _id: input.pageId, userId: uid })
|
||||
if (!page) {
|
||||
const item = await findLibraryItemById(input.pageId, uid)
|
||||
if (!item) {
|
||||
return {
|
||||
errorCodes: [RecommendHighlightsErrorCode.NotFound],
|
||||
}
|
||||
|
|
@ -366,18 +351,13 @@ export const recommendHighlightsResolver = authorized<
|
|||
.map((member) =>
|
||||
enqueueRecommendation(
|
||||
member.user.id,
|
||||
page.id,
|
||||
item.id,
|
||||
{
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
note: input.note,
|
||||
user: {
|
||||
userId: user.id,
|
||||
name: user.name,
|
||||
username: user.profile.username,
|
||||
profileImageURL: user.profile.pictureUrl,
|
||||
},
|
||||
recommendedAt: new Date(),
|
||||
recommender: user,
|
||||
createdAt: new Date(),
|
||||
libraryItem: item,
|
||||
},
|
||||
auth,
|
||||
input.highlightIds
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Rule } from '../../entity/rule'
|
||||
import { User } from '../../entity/user'
|
||||
import {
|
||||
DeleteRuleError,
|
||||
DeleteRuleErrorCode,
|
||||
|
|
@ -14,48 +13,28 @@ import {
|
|||
SetRuleErrorCode,
|
||||
SetRuleSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { getRepository } from '../../repository'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
|
||||
export const setRuleResolver = authorized<
|
||||
SetRuleSuccess,
|
||||
SetRuleError,
|
||||
MutationSetRuleArgs
|
||||
>(async (_, { input }, { claims, log }) => {
|
||||
log.info('Setting rules', {
|
||||
input,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'setRulesResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
>(async (_, { input }, { authTrx, uid, log }) => {
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: claims.uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [SetRuleErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const rule = await getRepository(Rule).save({
|
||||
...input,
|
||||
id: input.id || undefined,
|
||||
user: { id: claims.uid },
|
||||
})
|
||||
const rule = await authTrx((t) =>
|
||||
t.getRepository(Rule).save({
|
||||
...input,
|
||||
id: input.id || undefined,
|
||||
user: { id: uid },
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
rule,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error setting rules', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'setRulesResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
log.error('Error setting rules', error)
|
||||
|
||||
return {
|
||||
errorCodes: [SetRuleErrorCode.BadRequest],
|
||||
|
|
@ -68,23 +47,7 @@ export const rulesResolver = authorized<
|
|||
RulesError,
|
||||
QueryRulesArgs
|
||||
>(async (_, { enabled }, { claims, log }) => {
|
||||
log.info('Getting rules', {
|
||||
enabled,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'rulesResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: claims.uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [RulesErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const rules = await getRepository(Rule).findBy({
|
||||
user: { id: claims.uid },
|
||||
enabled: enabled === null ? undefined : enabled,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import Parser from 'rss-parser'
|
||||
import { Brackets } from 'typeorm'
|
||||
import { appDataSource } from '../../data_source'
|
||||
import { Subscription } from '../../entity/subscription'
|
||||
import { User } from '../../entity/user'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
MutationSubscribeArgs,
|
||||
|
|
@ -120,24 +118,28 @@ export const unsubscribeResolver = authorized<
|
|||
UnsubscribeSuccessPartial,
|
||||
UnsubscribeError,
|
||||
MutationUnsubscribeArgs
|
||||
>(async (_, { name, subscriptionId }, { claims: { uid }, log }) => {
|
||||
>(async (_, { name, subscriptionId }, { authTrx, uid, log }) => {
|
||||
log.info('unsubscribeResolver')
|
||||
|
||||
try {
|
||||
const queryBuilder = getRepository(Subscription)
|
||||
.createQueryBuilder('subscription')
|
||||
.leftJoinAndSelect('subscription.newsletterEmail', 'newsletterEmail')
|
||||
.where({ user: { id: uid } })
|
||||
const subscription = await authTrx(async (t) => {
|
||||
const queryBuilder = t
|
||||
.getRepository(Subscription)
|
||||
.createQueryBuilder('subscription')
|
||||
.leftJoinAndSelect('subscription.newsletterEmail', 'newsletterEmail')
|
||||
.where({ user: { id: uid } })
|
||||
|
||||
if (subscriptionId) {
|
||||
// if subscriptionId is provided, ignore name
|
||||
queryBuilder.andWhere({ id: subscriptionId })
|
||||
} else {
|
||||
// if subscriptionId is not provided, use name for old clients
|
||||
queryBuilder.andWhere({ name })
|
||||
}
|
||||
if (subscriptionId) {
|
||||
// if subscriptionId is provided, ignore name
|
||||
queryBuilder.andWhere({ id: subscriptionId })
|
||||
} else {
|
||||
// if subscriptionId is not provided, use name for old clients
|
||||
queryBuilder.andWhere({ name })
|
||||
}
|
||||
|
||||
return queryBuilder.getOne()
|
||||
})
|
||||
|
||||
const subscription = await queryBuilder.getOne()
|
||||
if (!subscription) {
|
||||
return {
|
||||
errorCodes: [UnsubscribeErrorCode.NotFound],
|
||||
|
|
@ -189,25 +191,20 @@ export const subscribeResolver = authorized<
|
|||
SubscribeSuccessPartial,
|
||||
SubscribeError,
|
||||
MutationSubscribeArgs
|
||||
>(async (_, { input }, { claims: { uid }, log }) => {
|
||||
>(async (_, { input }, { authTrx, uid, log }) => {
|
||||
log.info('subscribeResolver')
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [SubscribeErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
// find existing subscription
|
||||
const subscription = await getRepository(Subscription).findOneBy({
|
||||
url: input.url || undefined,
|
||||
name: input.name || undefined,
|
||||
user: { id: uid },
|
||||
status: SubscriptionStatus.Active,
|
||||
type: input.subscriptionType || SubscriptionType.Rss, // default to rss
|
||||
})
|
||||
const subscription = await authTrx((t) =>
|
||||
t.getRepository(Subscription).findOneBy({
|
||||
url: input.url || undefined,
|
||||
name: input.name || undefined,
|
||||
user: { id: uid },
|
||||
status: SubscriptionStatus.Active,
|
||||
type: input.subscriptionType || SubscriptionType.Rss, // default to rss
|
||||
})
|
||||
)
|
||||
if (subscription) {
|
||||
return {
|
||||
errorCodes: [SubscribeErrorCode.AlreadySubscribed],
|
||||
|
|
@ -223,30 +220,6 @@ export const subscribeResolver = authorized<
|
|||
},
|
||||
})
|
||||
|
||||
// create new newsletter subscription
|
||||
if (input.name && input.subscriptionType === SubscriptionType.Newsletter) {
|
||||
const subscribeHandler = getSubscribeHandler(input.name)
|
||||
if (!subscribeHandler) {
|
||||
return {
|
||||
errorCodes: [SubscribeErrorCode.NotFound],
|
||||
}
|
||||
}
|
||||
|
||||
const newSubscriptions = await subscribeHandler.handleSubscribe(
|
||||
uid,
|
||||
input.name
|
||||
)
|
||||
if (!newSubscriptions) {
|
||||
return {
|
||||
errorCodes: [SubscribeErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscriptions: newSubscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
// create new rss subscription
|
||||
if (input.url) {
|
||||
const MAX_RSS_SUBSCRIPTIONS = 150
|
||||
|
|
@ -254,21 +227,23 @@ export const subscribeResolver = authorized<
|
|||
const feed = await parser.parseURL(input.url)
|
||||
|
||||
// limit number of rss subscriptions to 50
|
||||
const newSubscriptions = (await appDataSource.query(
|
||||
`insert into omnivore.subscriptions (name, url, description, type, user_id, icon)
|
||||
const newSubscriptions = (await authTrx((t) =>
|
||||
t.query(
|
||||
`insert into omnivore.subscriptions (name, url, description, type, user_id, icon)
|
||||
select $1, $2, $3, $4, $5, $6 from omnivore.subscriptions
|
||||
where user_id = $5 and type = 'RSS' and status = 'ACTIVE'
|
||||
having count(*) < $7
|
||||
returning *;`,
|
||||
[
|
||||
feed.title,
|
||||
input.url,
|
||||
feed.description || null,
|
||||
SubscriptionType.Rss,
|
||||
uid,
|
||||
feed.image?.url || null,
|
||||
MAX_RSS_SUBSCRIPTIONS,
|
||||
]
|
||||
[
|
||||
feed.title,
|
||||
input.url,
|
||||
feed.description || null,
|
||||
SubscriptionType.Rss,
|
||||
uid,
|
||||
feed.image?.url || null,
|
||||
MAX_RSS_SUBSCRIPTIONS,
|
||||
]
|
||||
)
|
||||
)) as Subscription[]
|
||||
|
||||
if (newSubscriptions.length === 0) {
|
||||
|
|
|
|||
|
|
@ -1,53 +1,39 @@
|
|||
import { getPageById, updatePage } from '../../elastic/pages'
|
||||
import { Page } from '../../elastic/types'
|
||||
import { LibraryItem, LibraryItemState } from '../../entity/library_item'
|
||||
import {
|
||||
MutationUpdatePageArgs,
|
||||
UpdatePageError,
|
||||
UpdatePageErrorCode,
|
||||
UpdatePageSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { updateLibraryItem } from '../../services/library_item'
|
||||
import { Merge } from '../../util'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
|
||||
export type UpdatePageSuccessPartial = Merge<
|
||||
UpdatePageSuccess,
|
||||
{ updatedPage: Partial<Page> }
|
||||
{ updatedPage: Partial<LibraryItem> }
|
||||
>
|
||||
|
||||
export const updatePageResolver = authorized<
|
||||
UpdatePageSuccessPartial,
|
||||
UpdatePageError,
|
||||
MutationUpdatePageArgs
|
||||
>(async (_, { input }, ctx) => {
|
||||
const { pubsub, uid } = ctx
|
||||
|
||||
const page = await getPageById(input.pageId)
|
||||
|
||||
if (!page) {
|
||||
return { errorCodes: [UpdatePageErrorCode.NotFound] }
|
||||
}
|
||||
|
||||
const pageData = {
|
||||
id: input.pageId,
|
||||
title: input.title ?? undefined,
|
||||
description: input.description ?? undefined,
|
||||
author: input.byline ?? undefined,
|
||||
savedAt: input.savedAt ? new Date(input.savedAt) : undefined,
|
||||
publishedAt: input.publishedAt ? new Date(input.publishedAt) : undefined,
|
||||
image: input.previewImage ?? undefined,
|
||||
state: input.state ?? undefined,
|
||||
}
|
||||
|
||||
const updateResult = await updatePage(input.pageId, pageData, {
|
||||
pubsub: pubsub,
|
||||
uid,
|
||||
refresh: true,
|
||||
})
|
||||
if (!updateResult) return { errorCodes: [UpdatePageErrorCode.UpdateFailed] }
|
||||
|
||||
const updatedPage = (await getPageById(input.pageId)) as unknown as Page
|
||||
>(async (_, { input }, { uid }) => {
|
||||
const updatedPage = await updateLibraryItem(
|
||||
input.pageId,
|
||||
{
|
||||
title: input.title ?? undefined,
|
||||
description: input.description ?? undefined,
|
||||
author: input.byline ?? undefined,
|
||||
savedAt: input.savedAt ? new Date(input.savedAt) : undefined,
|
||||
publishedAt: input.publishedAt ? new Date(input.publishedAt) : undefined,
|
||||
thumbnail: input.previewImage ?? undefined,
|
||||
state: input.state
|
||||
? (input.state as unknown as LibraryItemState)
|
||||
: undefined,
|
||||
},
|
||||
uid
|
||||
)
|
||||
return {
|
||||
updatedPage: updatedPage,
|
||||
__typename: 'UpdatePageSuccess',
|
||||
updatedPage,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ import { appDataSource } from '../../data_source'
|
|||
import { RegistrationType, StatusType, User } from '../../entity/user'
|
||||
import { env } from '../../env'
|
||||
import { LoginErrorCode, SignupErrorCode } from '../../generated/graphql'
|
||||
import { getRepository, setClaims, userRepository } from '../../repository'
|
||||
import { getRepository, setClaims } from '../../repository'
|
||||
import { userRepository } from '../../repository/user'
|
||||
import { isErrorWithCode } from '../../resolvers'
|
||||
import { createUser, getUserByEmail } from '../../services/create_user'
|
||||
import { createUser } from '../../services/create_user'
|
||||
import {
|
||||
sendConfirmationEmail,
|
||||
sendPasswordResetEmail,
|
||||
|
|
@ -419,7 +420,7 @@ export function authRouter() {
|
|||
}
|
||||
const { email, password } = req.body
|
||||
try {
|
||||
const user = await getUserByEmail(email.trim())
|
||||
const user = await userRepository.findByEmail(email.trim())
|
||||
if (!user?.id) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/auth/email-login?errorCodes=${LoginErrorCode.UserNotFound}`
|
||||
|
|
@ -608,7 +609,7 @@ export function authRouter() {
|
|||
}
|
||||
|
||||
try {
|
||||
const user = await getUserByEmail(email)
|
||||
const user = await userRepository.findByEmail(email)
|
||||
if (!user) {
|
||||
return res.redirect(`${env.client.url}/auth/reset-sent`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { StatusType } from '../../../entity/user'
|
||||
import { getUserByEmail } from '../../../services/create_user'
|
||||
import { userRepository } from '../../../repository/user'
|
||||
import { sendConfirmationEmail } from '../../../services/send_emails'
|
||||
import { comparePassword } from '../../../utils/auth'
|
||||
import { logger } from '../../../utils/logger'
|
||||
|
|
@ -45,7 +45,7 @@ export async function createMobileEmailSignInResponse(
|
|||
throw new Error('Missing username or password')
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(email.trim())
|
||||
const user = await userRepository.findByEmail(email.trim())
|
||||
if (!user?.id || !user?.password) {
|
||||
throw new Error('user not found')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import cors from 'cors'
|
|||
import express, { Router } from 'express'
|
||||
import { env } from '../env'
|
||||
import { LoginErrorCode } from '../generated/graphql'
|
||||
import { userRepository } from '../repository'
|
||||
import { userRepository } from '../repository/user'
|
||||
import { createUser } from '../services/create_user'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { createWebAuthToken } from './auth/jwt_helpers'
|
||||
|
|
|
|||
|
|
@ -5,18 +5,19 @@
|
|||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import { createPage, getPageByParam, updatePage } from '../elastic/pages'
|
||||
import { addRecommendation } from '../elastic/recommendation'
|
||||
import { Recommendation } from '../elastic/types'
|
||||
import { LibraryItemState, LibraryItemType } from '../entity/library_item'
|
||||
import { Recommendation } from '../entity/recommendation'
|
||||
import { UploadFile } from '../entity/upload_file'
|
||||
import { env } from '../env'
|
||||
import {
|
||||
ArticleSavingRequestStatus,
|
||||
PageType,
|
||||
UploadFileStatus,
|
||||
} from '../generated/graphql'
|
||||
import { createPubSubClient } from '../pubsub'
|
||||
import { uploadFileRepository } from '../repository'
|
||||
import { UploadFileStatus } from '../generated/graphql'
|
||||
import { authTrx } from '../repository'
|
||||
import { Claims } from '../resolvers/types'
|
||||
import {
|
||||
createLibraryItem,
|
||||
findLibraryItemById,
|
||||
findLibraryItemByUrl,
|
||||
updateLibraryItem,
|
||||
} from '../services/library_item'
|
||||
import { getTokenByRequest } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import {
|
||||
|
|
@ -75,20 +76,20 @@ export function pageRouter() {
|
|||
return res.status(400).send({ errorCode: 'BAD_DATA' })
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
uid: claims.uid,
|
||||
pubsub: createPubSubClient(),
|
||||
}
|
||||
|
||||
const title = titleForFilePath(url)
|
||||
const fileName = fileNameForFilePath(url)
|
||||
const uploadFileData = await uploadFileRepository.save({
|
||||
url,
|
||||
userId: claims.uid,
|
||||
fileName,
|
||||
status: UploadFileStatus.Initialized,
|
||||
contentType: 'application/pdf',
|
||||
})
|
||||
const uploadFileData = await authTrx(
|
||||
(t) =>
|
||||
t.getRepository(UploadFile).save({
|
||||
url,
|
||||
userId: claims.uid,
|
||||
fileName,
|
||||
status: UploadFileStatus.Initialized,
|
||||
contentType: 'application/pdf',
|
||||
}),
|
||||
undefined,
|
||||
claims.uid
|
||||
)
|
||||
|
||||
const uploadFilePathName = generateUploadFilePathName(
|
||||
uploadFileData.id,
|
||||
|
|
@ -100,45 +101,35 @@ export function pageRouter() {
|
|||
'application/pdf'
|
||||
)
|
||||
|
||||
const page = await getPageByParam({
|
||||
userId: claims.uid,
|
||||
url: url,
|
||||
})
|
||||
const item = await findLibraryItemByUrl(url, claims.uid)
|
||||
|
||||
if (page) {
|
||||
if (item) {
|
||||
logger.info('updating page')
|
||||
await updatePage(
|
||||
page.id,
|
||||
await updateLibraryItem(
|
||||
item.id,
|
||||
{
|
||||
savedAt: new Date(),
|
||||
archivedAt: null,
|
||||
state: LibraryItemState.Succeeded,
|
||||
},
|
||||
ctx
|
||||
claims.uid
|
||||
)
|
||||
} else {
|
||||
logger.info('creating page')
|
||||
const pageId = await createPage(
|
||||
await createLibraryItem(
|
||||
{
|
||||
url: signedUrl,
|
||||
originalUrl: signedUrl,
|
||||
id: clientRequestId,
|
||||
userId: claims.uid,
|
||||
title: title,
|
||||
hash: uploadFilePathName,
|
||||
content: '',
|
||||
pageType: PageType.File,
|
||||
uploadFileId: uploadFileData.id,
|
||||
user: { id: claims.uid },
|
||||
title,
|
||||
originalContent: '',
|
||||
itemType: LibraryItemType.File,
|
||||
uploadFile: { id: uploadFileData.id },
|
||||
slug: generateSlug(uploadFilePathName),
|
||||
createdAt: new Date(),
|
||||
savedAt: new Date(),
|
||||
readingProgressPercent: 0,
|
||||
readingProgressAnchorIndex: 0,
|
||||
state: ArticleSavingRequestStatus.Processing,
|
||||
state: LibraryItemState.Processing,
|
||||
},
|
||||
ctx
|
||||
claims.uid
|
||||
)
|
||||
if (!pageId) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('redirecting to signed URL', signedUrl)
|
||||
|
|
@ -170,16 +161,8 @@ export function pageRouter() {
|
|||
return res.status(400).send({ errorCode: 'BAD_DATA' })
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
uid: userId,
|
||||
pubsub: createPubSubClient(),
|
||||
}
|
||||
|
||||
const page = await getPageByParam({
|
||||
userId: claims.uid,
|
||||
_id: pageId,
|
||||
})
|
||||
if (!page) {
|
||||
const item = await findLibraryItemById(pageId, userId)
|
||||
if (!item) {
|
||||
return res.status(404).send({ errorCode: 'NOT_FOUND' })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import express from 'express'
|
||||
import { getPageByParam, updatePage } from '../../elastic/pages'
|
||||
import { Page } from '../../elastic/types'
|
||||
import { ArticleSavingRequestStatus } from '../../generated/graphql'
|
||||
import { createPubSubClient, readPushSubscription } from '../../pubsub'
|
||||
import { LibraryItem, LibraryItemState } from '../../entity/library_item'
|
||||
import { readPushSubscription } from '../../pubsub'
|
||||
import { authTrx } from '../../repository'
|
||||
import { libraryItemRepository } from '../../repository/library_item'
|
||||
import { updateLibraryItem } from '../../services/library_item'
|
||||
import { setFileUploadComplete } from '../../services/upload_file'
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ export function contentServiceRouter() {
|
|||
logger.info('read pubsub message', msgStr, 'has expired', expired)
|
||||
|
||||
if (!msgStr) {
|
||||
res.status(400).send('Bad Request')
|
||||
res.status(200).send('Bad Request')
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ export function contentServiceRouter() {
|
|||
const data = JSON.parse(msgStr)
|
||||
if (!('fileId' in data) || !('content' in data)) {
|
||||
logger.info('No file id or content found in message')
|
||||
res.status(400).send('Bad Request')
|
||||
res.status(200).send('Bad Request')
|
||||
return
|
||||
}
|
||||
const msg = data as UpdateContentMessage
|
||||
|
|
@ -50,43 +50,53 @@ export function contentServiceRouter() {
|
|||
const fileId = parts && parts.length > 1 ? parts[1] : undefined
|
||||
if (!fileId) {
|
||||
logger.info('No file id found in message')
|
||||
res.status(400).send('Bad Request')
|
||||
res.status(200).send('Bad Request')
|
||||
return
|
||||
}
|
||||
|
||||
const page = await getPageByParam({ uploadFileId: fileId })
|
||||
if (!page) {
|
||||
const libraryItem = await authTrx(async (tx) =>
|
||||
tx
|
||||
.withRepository(libraryItemRepository)
|
||||
.createQueryBuilder('item')
|
||||
.innerJoinAndSelect('item.user', 'user')
|
||||
.innerJoinAndSelect('item.uploadFile', 'file')
|
||||
.where('item.fileId = :fileId', { fileId })
|
||||
.getOne()
|
||||
)
|
||||
if (!libraryItem) {
|
||||
logger.info('No upload file found for id:', fileId)
|
||||
res.status(400).send('Bad Request')
|
||||
return
|
||||
}
|
||||
|
||||
const pageToUpdate: Partial<Page> = { content: msg.content }
|
||||
if (msg.title) pageToUpdate.title = msg.title
|
||||
if (msg.author) pageToUpdate.author = msg.author
|
||||
if (msg.description) pageToUpdate.description = msg.description
|
||||
const itemToUpdate: Partial<LibraryItem> = { originalContent: msg.content }
|
||||
if (msg.title) itemToUpdate.title = msg.title
|
||||
if (msg.author) itemToUpdate.author = msg.author
|
||||
if (msg.description) itemToUpdate.description = msg.description
|
||||
|
||||
// This event is fired after the file is fully uploaded,
|
||||
// so along with upadting content, we mark it as
|
||||
// so along with updateing content, we mark it as
|
||||
// succeeded.
|
||||
pageToUpdate.state = ArticleSavingRequestStatus.Succeeded
|
||||
itemToUpdate.state = LibraryItemState.Succeeded
|
||||
|
||||
try {
|
||||
const uploadFileData = await authTrx(async (t) =>
|
||||
setFileUploadComplete(fileId)
|
||||
const uploadFileData = await setFileUploadComplete(
|
||||
fileId,
|
||||
libraryItem.user.id
|
||||
)
|
||||
logger.info('updated uploadFileData', uploadFileData)
|
||||
} catch (error) {
|
||||
logger.info('error marking file upload as completed', error)
|
||||
}
|
||||
|
||||
const result = await updatePage(page.id, pageToUpdate, {
|
||||
pubsub: createPubSubClient(),
|
||||
uid: page.userId,
|
||||
})
|
||||
const result = await updateLibraryItem(
|
||||
libraryItem.id,
|
||||
itemToUpdate,
|
||||
libraryItem.user.id
|
||||
)
|
||||
logger.info(
|
||||
'Updating article text',
|
||||
page.id,
|
||||
'Updating library item text',
|
||||
libraryItem.id,
|
||||
result,
|
||||
msg.content.substring(0, 20)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
import express from 'express'
|
||||
import { appDataSource } from '../../data_source'
|
||||
import { createPage } from '../../elastic/pages'
|
||||
import { ArticleSavingRequestStatus, Page } from '../../elastic/types'
|
||||
import { DeepPartial } from 'typeorm'
|
||||
import {
|
||||
LibraryItem,
|
||||
LibraryItemState,
|
||||
LibraryItemType,
|
||||
} from '../../entity/library_item'
|
||||
import { UploadFile } from '../../entity/upload_file'
|
||||
import { env } from '../../env'
|
||||
import { PageType, UploadFileStatus } from '../../generated/graphql'
|
||||
import { createPubSubClient } from '../../pubsub'
|
||||
import { setClaims, uploadFileRepository } from '../../repository'
|
||||
import { UploadFileStatus } from '../../generated/graphql'
|
||||
import { authTrx } from '../../repository'
|
||||
import { createLibraryItem } from '../../services/library_item'
|
||||
import { getNewsletterEmail } from '../../services/newsletters'
|
||||
import { updateReceivedEmail } from '../../services/received_emails'
|
||||
import { setFileUploadComplete } from '../../services/save_file'
|
||||
import {
|
||||
findUploadFileById,
|
||||
setFileUploadComplete,
|
||||
} from '../../services/upload_file'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { getClaimsByToken } from '../../utils/auth'
|
||||
import { generateSlug } from '../../utils/helpers'
|
||||
|
|
@ -54,13 +61,15 @@ export function emailAttachmentRouter() {
|
|||
})
|
||||
|
||||
try {
|
||||
const uploadFileData = await uploadFileRepository.save({
|
||||
url: '',
|
||||
userId: user.id,
|
||||
fileName: fileName,
|
||||
status: UploadFileStatus.Initialized,
|
||||
contentType: contentType,
|
||||
})
|
||||
const uploadFileData = await authTrx((tx) =>
|
||||
tx.getRepository(UploadFile).save({
|
||||
url: '',
|
||||
userId: user.id,
|
||||
fileName: fileName,
|
||||
status: UploadFileStatus.Initialized,
|
||||
contentType: contentType,
|
||||
})
|
||||
)
|
||||
|
||||
if (uploadFileData.id) {
|
||||
const uploadFilePathName = generateUploadFilePathName(
|
||||
|
|
@ -116,10 +125,7 @@ export function emailAttachmentRouter() {
|
|||
})
|
||||
|
||||
try {
|
||||
const uploadFile = await uploadFileRepository.findOneBy({
|
||||
id: uploadFileId,
|
||||
user: { id: user.id },
|
||||
})
|
||||
const uploadFile = await findUploadFileById(uploadFileId)
|
||||
if (!uploadFile) {
|
||||
return res.status(400).send('BAD REQUEST')
|
||||
}
|
||||
|
|
@ -129,10 +135,7 @@ export function emailAttachmentRouter() {
|
|||
uploadFile.fileName
|
||||
)
|
||||
|
||||
const uploadFileData = await appDataSource.transaction(async (tx) => {
|
||||
await setClaims(tx, user.id)
|
||||
return setFileUploadComplete(uploadFileId, tx)
|
||||
})
|
||||
const uploadFileData = await setFileUploadComplete(uploadFileId, user.id)
|
||||
if (!uploadFileData || !uploadFileData.id || !uploadFileData.fileName) {
|
||||
return res.status(400).send('BAD REQUEST')
|
||||
}
|
||||
|
|
@ -143,32 +146,23 @@ export function emailAttachmentRouter() {
|
|||
)
|
||||
|
||||
const uploadFileHash = uploadFileDetails.md5Hash
|
||||
const pageType =
|
||||
const itemType =
|
||||
uploadFile.contentType === 'application/pdf'
|
||||
? PageType.File
|
||||
: PageType.Book
|
||||
? LibraryItemType.File
|
||||
: LibraryItemType.Book
|
||||
const title = subject || uploadFileData.fileName
|
||||
const articleToSave: Page = {
|
||||
id: '',
|
||||
url: uploadFileUrlOverride,
|
||||
pageType,
|
||||
hash: uploadFileHash,
|
||||
uploadFileId,
|
||||
const articleToSave: DeepPartial<LibraryItem> = {
|
||||
originalUrl: uploadFileUrlOverride,
|
||||
itemType,
|
||||
textContentHash: uploadFileHash,
|
||||
uploadFile: { id: uploadFileData.id },
|
||||
title,
|
||||
content: '',
|
||||
userId: user.id,
|
||||
readableContent: '',
|
||||
slug: generateSlug(title),
|
||||
createdAt: new Date(),
|
||||
savedAt: new Date(),
|
||||
readingProgressPercent: 0,
|
||||
readingProgressAnchorIndex: 0,
|
||||
state: ArticleSavingRequestStatus.Succeeded,
|
||||
state: LibraryItemState.Succeeded,
|
||||
}
|
||||
|
||||
const pageId = await createPage(articleToSave, {
|
||||
pubsub: createPubSubClient(),
|
||||
uid: user.id,
|
||||
})
|
||||
const pageId = await createLibraryItem(articleToSave, user.id)
|
||||
|
||||
// update received email type
|
||||
await updateReceivedEmail(receivedEmailId, 'article')
|
||||
|
|
|
|||
|
|
@ -5,13 +5,16 @@ import { stringify } from 'csv-stringify'
|
|||
import express from 'express'
|
||||
import { DateTime } from 'luxon'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getPageById, searchLibraryItems } from '../../elastic/pages'
|
||||
import { Page } from '../../elastic/types'
|
||||
import { Integration, IntegrationType } from '../../entity/integration'
|
||||
import { LibraryItem } from '../../entity/library_item'
|
||||
import { EntityType, readPushSubscription } from '../../pubsub'
|
||||
import { getRepository } from '../../repository'
|
||||
import { Claims } from '../../resolvers/types'
|
||||
import { getIntegrationService } from '../../services/integrations'
|
||||
import {
|
||||
findLibraryItemById,
|
||||
searchLibraryItems,
|
||||
} from '../../services/library_item'
|
||||
import { getClaimsByToken } from '../../utils/auth'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { DateFilter } from '../../utils/search'
|
||||
|
|
@ -95,13 +98,13 @@ export function integrationsServiceRouter() {
|
|||
res.status(200).send('Bad Request')
|
||||
return
|
||||
}
|
||||
const page = await getPageById(id)
|
||||
const page = await findLibraryItemById(id, userId)
|
||||
if (!page) {
|
||||
logger.info('No page found for id', { id })
|
||||
res.status(200).send('No page found')
|
||||
return
|
||||
}
|
||||
if (page.userId !== userId) {
|
||||
if (page.user.id !== userId) {
|
||||
logger.info('Page does not belong to user', { id, userId })
|
||||
return res.status(200).send('Page does not belong to user')
|
||||
}
|
||||
|
|
@ -124,7 +127,10 @@ export function integrationsServiceRouter() {
|
|||
const size = 50
|
||||
|
||||
for (
|
||||
let hasNextPage = true, count = 0, after = 0, pages: Page[] = [];
|
||||
let hasNextPage = true,
|
||||
count = 0,
|
||||
after = 0,
|
||||
pages: LibraryItem[] = [];
|
||||
hasNextPage;
|
||||
after += size, hasNextPage = count > after
|
||||
) {
|
||||
|
|
@ -133,10 +139,11 @@ export function integrationsServiceRouter() {
|
|||
const dateFilters: DateFilter[] = []
|
||||
syncedAt &&
|
||||
dateFilters.push({ field: 'updatedAt', startDate: syncedAt })
|
||||
;[pages, count] = (await searchLibraryItems(
|
||||
const { libraryItems } = await searchLibraryItems(
|
||||
{ from: after, size, dateFilters },
|
||||
userId
|
||||
)) as [Page[], number]
|
||||
)
|
||||
pages = libraryItems
|
||||
const pageIds = pages.map((p) => p.id)
|
||||
|
||||
logger.info('syncing pages', { pageIds })
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { homePageURL } from '../../env'
|
|||
import { ContentReader } from '../../generated/graphql'
|
||||
import { createPubSubClient } from '../../pubsub'
|
||||
import { setClaims } from '../../repository'
|
||||
import { PageReminder, setRemindersComplete } from '../../services/reminders'
|
||||
|
||||
interface PageToNotify {
|
||||
title: string
|
||||
|
|
@ -127,115 +126,115 @@ interface PageToNotify {
|
|||
// return router
|
||||
// }
|
||||
|
||||
const getPagesToNotifyAndUnarchive = (
|
||||
pageReminders: PageReminder[],
|
||||
username: string
|
||||
): [pages: PageToNotify[], linkIds: string[]] => {
|
||||
const pageIds: string[] = []
|
||||
const pages: PageToNotify[] = []
|
||||
pageReminders.forEach((pageReminder) => {
|
||||
pageIds.push(pageReminder.pageId)
|
||||
// const getPagesToNotifyAndUnarchive = (
|
||||
// pageReminders: PageReminder[],
|
||||
// username: string
|
||||
// ): [pages: PageToNotify[], linkIds: string[]] => {
|
||||
// const pageIds: string[] = []
|
||||
// const pages: PageToNotify[] = []
|
||||
// pageReminders.forEach((pageReminder) => {
|
||||
// pageIds.push(pageReminder.pageId)
|
||||
|
||||
pageReminder.sendNotification &&
|
||||
pages.push({
|
||||
url: `${homePageURL()}/${username}/${pageReminder.slug}`,
|
||||
title: pageReminder.title,
|
||||
description: pageReminder.description,
|
||||
byline: pageReminder.author,
|
||||
image: pageReminder.image,
|
||||
})
|
||||
})
|
||||
// pageReminder.sendNotification &&
|
||||
// pages.push({
|
||||
// url: `${homePageURL()}/${username}/${pageReminder.slug}`,
|
||||
// title: pageReminder.title,
|
||||
// description: pageReminder.description,
|
||||
// byline: pageReminder.author,
|
||||
// image: pageReminder.image,
|
||||
// })
|
||||
// })
|
||||
|
||||
return [pages, pageIds]
|
||||
}
|
||||
// return [pages, pageIds]
|
||||
// }
|
||||
|
||||
const messageForPages = (
|
||||
pageReminders: PageReminder[],
|
||||
deviceTokens: UserDeviceToken[]
|
||||
): MulticastMessage => {
|
||||
const pages = pageReminders.filter((reminder) => reminder.sendNotification)
|
||||
// const messageForPages = (
|
||||
// pageReminders: PageReminder[],
|
||||
// deviceTokens: UserDeviceToken[]
|
||||
// ): MulticastMessage => {
|
||||
// const pages = pageReminders.filter((reminder) => reminder.sendNotification)
|
||||
|
||||
// If the user only has one reminder triggered we send a deep
|
||||
// link to that link.
|
||||
if (pages.length === 1) {
|
||||
const page = pages[0]
|
||||
let title = 'Snoozed: You have one snoozed article to read on Omnivore'
|
||||
// // If the user only has one reminder triggered we send a deep
|
||||
// // link to that link.
|
||||
// if (pages.length === 1) {
|
||||
// const page = pages[0]
|
||||
// let title = 'Snoozed: You have one snoozed article to read on Omnivore'
|
||||
|
||||
if (page.author) {
|
||||
title = `'Snoozed: From ${page.author}`
|
||||
}
|
||||
// if (page.author) {
|
||||
// title = `'Snoozed: From ${page.author}`
|
||||
// }
|
||||
|
||||
const pushData = !page
|
||||
? undefined
|
||||
: {
|
||||
link: Buffer.from(
|
||||
JSON.stringify({
|
||||
id: page.pageId,
|
||||
url: page.url,
|
||||
slug: page.slug,
|
||||
title: page.title,
|
||||
image: page.image,
|
||||
author: page.author,
|
||||
isArchived: false,
|
||||
contentReader: ContentReader.Web,
|
||||
readingProgressPercent: 0,
|
||||
readingProgressAnchorIndex: 0,
|
||||
})
|
||||
).toString('base64'),
|
||||
}
|
||||
// const pushData = !page
|
||||
// ? undefined
|
||||
// : {
|
||||
// link: Buffer.from(
|
||||
// JSON.stringify({
|
||||
// id: page.pageId,
|
||||
// url: page.url,
|
||||
// slug: page.slug,
|
||||
// title: page.title,
|
||||
// image: page.image,
|
||||
// author: page.author,
|
||||
// isArchived: false,
|
||||
// contentReader: ContentReader.Web,
|
||||
// readingProgressPercent: 0,
|
||||
// readingProgressAnchorIndex: 0,
|
||||
// })
|
||||
// ).toString('base64'),
|
||||
// }
|
||||
|
||||
return {
|
||||
notification: {
|
||||
title,
|
||||
body: page.title,
|
||||
imageUrl: page.image || undefined,
|
||||
},
|
||||
data: pushData,
|
||||
tokens: deviceTokens.map((token) => token.token),
|
||||
}
|
||||
}
|
||||
// return {
|
||||
// notification: {
|
||||
// title,
|
||||
// body: page.title,
|
||||
// imageUrl: page.image || undefined,
|
||||
// },
|
||||
// data: pushData,
|
||||
// tokens: deviceTokens.map((token) => token.token),
|
||||
// }
|
||||
// }
|
||||
|
||||
const title = `Snoozed: You have ${pages.length} articles to read.`
|
||||
let description = 'Read them now.'
|
||||
const allBylines = pages.map((page) => page.author).filter((byline) => byline)
|
||||
const bylines = [...new Set(allBylines)].splice(0, 5)
|
||||
if (bylines.length > 0) {
|
||||
description = 'From ' + bylines.map((byline) => byline).join(', ')
|
||||
}
|
||||
// const title = `Snoozed: You have ${pages.length} articles to read.`
|
||||
// let description = 'Read them now.'
|
||||
// const allBylines = pages.map((page) => page.author).filter((byline) => byline)
|
||||
// const bylines = [...new Set(allBylines)].splice(0, 5)
|
||||
// if (bylines.length > 0) {
|
||||
// description = 'From ' + bylines.map((byline) => byline).join(', ')
|
||||
// }
|
||||
|
||||
return {
|
||||
notification: {
|
||||
title: title,
|
||||
body: description,
|
||||
},
|
||||
tokens: deviceTokens.map((token) => token.token),
|
||||
}
|
||||
}
|
||||
// return {
|
||||
// notification: {
|
||||
// title: title,
|
||||
// body: description,
|
||||
// },
|
||||
// tokens: deviceTokens.map((token) => token.token),
|
||||
// }
|
||||
// }
|
||||
|
||||
const updateRemindersStatus = async (
|
||||
userId: string,
|
||||
pagesToUnarchive: string[],
|
||||
remindAt: Date
|
||||
): Promise<void> => {
|
||||
// Unarchive all the links and updated saved_at to now, so they
|
||||
// appear at the top of the user's list.
|
||||
for (const pageId of pagesToUnarchive) {
|
||||
await updatePage(
|
||||
pageId,
|
||||
{
|
||||
savedAt: new Date(),
|
||||
archivedAt: null,
|
||||
},
|
||||
{
|
||||
pubsub: createPubSubClient(),
|
||||
uid: userId,
|
||||
}
|
||||
)
|
||||
}
|
||||
// const updateRemindersStatus = async (
|
||||
// userId: string,
|
||||
// pagesToUnarchive: string[],
|
||||
// remindAt: Date
|
||||
// ): Promise<void> => {
|
||||
// // Unarchive all the links and updated saved_at to now, so they
|
||||
// // appear at the top of the user's list.
|
||||
// for (const pageId of pagesToUnarchive) {
|
||||
// await updatePage(
|
||||
// pageId,
|
||||
// {
|
||||
// savedAt: new Date(),
|
||||
// archivedAt: null,
|
||||
// },
|
||||
// {
|
||||
// pubsub: createPubSubClient(),
|
||||
// uid: userId,
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// db update
|
||||
await appDataSource.transaction(async (tx) => {
|
||||
await setClaims(tx, userId)
|
||||
await setRemindersComplete(tx, userId, remindAt)
|
||||
})
|
||||
}
|
||||
// // db update
|
||||
// await appDataSource.transaction(async (tx) => {
|
||||
// await setClaims(tx, userId)
|
||||
// await setRemindersComplete(tx, userId, remindAt)
|
||||
// })
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,123 +1,17 @@
|
|||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { htmlToSpeechFile } from '@omnivore/text-to-speech-handler'
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import { appDataSource } from '../data_source'
|
||||
import { getPageById } from '../elastic/pages'
|
||||
import { ArticleSavingRequestStatus } from '../elastic/types'
|
||||
import { Speech, SpeechState } from '../entity/speech'
|
||||
import { UserPersonalization } from '../entity/user_personalization'
|
||||
import { readPushSubscription } from '../pubsub'
|
||||
import { getRepository, setClaims } from '../repository'
|
||||
import { FeatureName, getFeature } from '../services/features'
|
||||
import { shouldSynthesize } from '../services/speech'
|
||||
import { authTrx } from '../repository'
|
||||
import { getClaimsByToken } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { enqueueTextToSpeech } from '../utils/createTask'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
const DEFAULT_VOICE = 'Larry'
|
||||
const DEFAULT_COMPLIMENTARY_VOICE = 'Evelyn'
|
||||
|
||||
export function textToSpeechRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
router.post('/auto-synthesize', async (req, res) => {
|
||||
logger.info('auto-synthesize')
|
||||
const { message: msgStr, expired } = readPushSubscription(req)
|
||||
|
||||
if (!msgStr) {
|
||||
return res.status(400).send('Bad Request')
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
logger.info('discarding expired message')
|
||||
return res.status(200).send('Expired')
|
||||
}
|
||||
|
||||
try {
|
||||
const data: { userId: string; type: string; id: string } =
|
||||
JSON.parse(msgStr)
|
||||
const { userId, type, id } = data
|
||||
if (!userId || !type || !id) {
|
||||
logger.info('Invalid data')
|
||||
return res.status(400).send('Bad Request')
|
||||
}
|
||||
|
||||
if (type.toUpperCase() !== 'PAGE') {
|
||||
logger.info('Not a page')
|
||||
return res.status(200).send('Not a page')
|
||||
}
|
||||
|
||||
const page = await getPageById(id)
|
||||
if (!page) {
|
||||
logger.info('No page found', { id })
|
||||
return res.status(200).send('No page found')
|
||||
}
|
||||
if (page.userId !== userId) {
|
||||
logger.info('Page does not belong to user', { id, userId })
|
||||
return res.status(200).send('Page does not belong to user')
|
||||
}
|
||||
if (page.state === ArticleSavingRequestStatus.Processing) {
|
||||
logger.info('Page is still processing, try again later', { id })
|
||||
return res.status(400).send('Page is still processing')
|
||||
}
|
||||
|
||||
// checks if this page needs to be synthesized automatically
|
||||
if (await shouldSynthesize(userId, page)) {
|
||||
logger.info('page needs to be synthesized')
|
||||
|
||||
const userPersonalization = await getRepository(
|
||||
UserPersonalization
|
||||
).findOneBy({ user: { id: userId } })
|
||||
|
||||
const speechFile = htmlToSpeechFile({
|
||||
title: page.title,
|
||||
content: page.content,
|
||||
options: {
|
||||
primaryVoice: userPersonalization?.speechVoice || DEFAULT_VOICE,
|
||||
secondaryVoice:
|
||||
userPersonalization?.speechSecondaryVoice ||
|
||||
DEFAULT_COMPLIMENTARY_VOICE,
|
||||
language: page.language,
|
||||
},
|
||||
})
|
||||
|
||||
const feature = await getFeature(
|
||||
FeatureName.UltraRealisticVoice,
|
||||
userId
|
||||
)
|
||||
|
||||
for (const utterance of speechFile.utterances) {
|
||||
// enqueue a task to convert text to speech
|
||||
const taskName = await enqueueTextToSpeech({
|
||||
userId,
|
||||
speechId: utterance.idx,
|
||||
text: utterance.text,
|
||||
voice: utterance.voice || DEFAULT_VOICE,
|
||||
priority: 'high',
|
||||
isUltraRealisticVoice: true,
|
||||
language: speechFile.language,
|
||||
rate: userPersonalization?.speechRate || '1.1',
|
||||
featureName: feature?.name,
|
||||
grantedAt: feature?.grantedAt,
|
||||
})
|
||||
logger.info('Start Text to speech task', { taskName })
|
||||
}
|
||||
|
||||
return res.status(202).send('Text to speech task started')
|
||||
}
|
||||
|
||||
res.status(200).send('Page should not synthesize')
|
||||
} catch (err) {
|
||||
logger.error('Auto synthesize failed', err)
|
||||
res.status(500).send(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.options('/', cors<express.Request>({ ...corsConfig, maxAge: 600 }))
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
router.post('/', async (req, res) => {
|
||||
|
|
@ -151,14 +45,17 @@ export function textToSpeechRouter() {
|
|||
}
|
||||
|
||||
// set state to completed
|
||||
await appDataSource.transaction(async (t) => {
|
||||
await setClaims(t, userId)
|
||||
await t.getRepository(Speech).update(speechId, {
|
||||
audioFileName: audioFileName,
|
||||
speechMarksFileName: speechMarksFileName,
|
||||
state,
|
||||
})
|
||||
})
|
||||
await authTrx(
|
||||
async (t) => {
|
||||
await t.getRepository(Speech).update(speechId, {
|
||||
audioFileName: audioFileName,
|
||||
speechMarksFileName: speechMarksFileName,
|
||||
state,
|
||||
})
|
||||
},
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
|
||||
res.send('OK')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import * as privateIpLib from 'private-ip'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { countByCreatedAt } from '../elastic/pages'
|
||||
import { ArticleSavingRequestStatus, PageType } from '../elastic/types'
|
||||
import { LibraryItemState, LibraryItemType } from '../entity/library_item'
|
||||
import {
|
||||
ArticleSavingRequest,
|
||||
ArticleSavingRequestStatus,
|
||||
CreateArticleSavingRequestErrorCode,
|
||||
CreateLabelInput,
|
||||
} from '../generated/graphql'
|
||||
|
|
@ -18,6 +17,7 @@ import {
|
|||
} from '../utils/helpers'
|
||||
import { logger } from '../utils/logger'
|
||||
import {
|
||||
countByCreatedAt,
|
||||
createLibraryItem,
|
||||
findLibraryItemByUrl,
|
||||
updateLibraryItem,
|
||||
|
|
@ -46,7 +46,7 @@ const isPrivateIP = privateIpLib.default
|
|||
const getPriorityByRateLimit = async (
|
||||
userId: string
|
||||
): Promise<'low' | 'high'> => {
|
||||
const count = await countByCreatedAt(userId, Date.now() - 60 * 1000)
|
||||
const count = await countByCreatedAt(new Date(Date.now() - 60 * 1000))
|
||||
return count >= 5 ? 'low' : 'high'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import { Invite } from '../entity/groups/invite'
|
|||
import { Profile } from '../entity/profile'
|
||||
import { StatusType, User } from '../entity/user'
|
||||
import { SignupErrorCode } from '../generated/graphql'
|
||||
import { authTrx, entityManager } from '../repository'
|
||||
import { profileRepository } from '../repository/profile'
|
||||
import { authTrx, entityManager, getRepository } from '../repository'
|
||||
import { userRepository } from '../repository/user'
|
||||
import { AuthProvider } from '../routers/auth/auth_types'
|
||||
import { logger } from '../utils/logger'
|
||||
|
|
@ -48,7 +47,7 @@ export const createUser = async (input: {
|
|||
}
|
||||
|
||||
// create profile if user exists but profile does not exist
|
||||
const profile = await profileRepository.save({
|
||||
const profile = await getRepository(Profile).save({
|
||||
username: input.username,
|
||||
pictureUrl: input.pictureUrl,
|
||||
bio: input.bio,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { nanoid } from 'nanoid'
|
||||
import { appDataSource } from '../data_source'
|
||||
import { Group } from '../entity/groups/group'
|
||||
import { GroupMembership } from '../entity/groups/group_membership'
|
||||
import { Invite } from '../entity/groups/invite'
|
||||
|
|
@ -7,8 +6,7 @@ import { RuleActionType } from '../entity/rule'
|
|||
import { User } from '../entity/user'
|
||||
import { homePageURL } from '../env'
|
||||
import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql'
|
||||
import { authTrx } from '../repository'
|
||||
import { groupRepository } from '../repository/group'
|
||||
import { entityManager, getRepository } from '../repository'
|
||||
import { userDataToUser } from '../utils/helpers'
|
||||
import { getLabelsAndCreateIfNotExist } from './labels'
|
||||
import { createRule } from './rules'
|
||||
|
|
@ -23,7 +21,7 @@ export const createGroup = async (input: {
|
|||
onlyAdminCanPost?: boolean | null
|
||||
onlyAdminCanSeeMembers?: boolean | null
|
||||
}): Promise<[Group, Invite]> => {
|
||||
const [group, invite] = await appDataSource.transaction<[Group, Invite]>(
|
||||
const [group, invite] = await entityManager.transaction<[Group, Invite]>(
|
||||
async (t) => {
|
||||
// Max number of groups a user can create
|
||||
const maxGroups = 3
|
||||
|
|
@ -72,12 +70,10 @@ export const createGroup = async (input: {
|
|||
export const getRecommendationGroups = async (
|
||||
user: User
|
||||
): Promise<RecommendationGroup[]> => {
|
||||
const groupMembers = await authTrx((t) =>
|
||||
t.getRepository(GroupMembership).find({
|
||||
where: { user: { id: user.id } },
|
||||
relations: ['invite', 'group.members.user.profile'],
|
||||
})
|
||||
)
|
||||
const groupMembers = await getRepository(GroupMembership).find({
|
||||
where: { user: { id: user.id } },
|
||||
relations: ['invite', 'group.members.user.profile'],
|
||||
})
|
||||
|
||||
return groupMembers.map((gm) => {
|
||||
const admins: GraphqlUser[] = []
|
||||
|
|
@ -117,7 +113,7 @@ export const joinGroup = async (
|
|||
user: User,
|
||||
inviteCode: string
|
||||
): Promise<RecommendationGroup> => {
|
||||
const invite = await appDataSource.transaction<Invite>(async (t) => {
|
||||
const invite = await entityManager.transaction<Invite>(async (t) => {
|
||||
// Check if the invite exists
|
||||
const invite = await t
|
||||
.getRepository(Invite)
|
||||
|
|
@ -146,7 +142,7 @@ export const joinGroup = async (
|
|||
return invite
|
||||
})
|
||||
|
||||
const group = await groupRepository.findOneOrFail({
|
||||
const group = await getRepository(Group).findOneOrFail({
|
||||
where: { id: invite.group.id },
|
||||
relations: ['members', 'members.user.profile'],
|
||||
})
|
||||
|
|
@ -177,7 +173,7 @@ export const leaveGroup = async (
|
|||
user: User,
|
||||
groupId: string
|
||||
): Promise<boolean> => {
|
||||
return authTrx(async (t) => {
|
||||
return entityManager.transaction(async (t) => {
|
||||
const group = await t
|
||||
.getRepository(Group)
|
||||
.createQueryBuilder('group')
|
||||
|
|
@ -276,7 +272,7 @@ export const getGroupsWhereUserCanPost = async (
|
|||
userId: string,
|
||||
groupIds: string[]
|
||||
): Promise<Group[]> => {
|
||||
return groupRepository
|
||||
return getRepository(Group)
|
||||
.createQueryBuilder('group')
|
||||
.innerJoin('group.members', 'members1')
|
||||
.whereInIds(groupIds)
|
||||
|
|
|
|||
|
|
@ -276,14 +276,34 @@ export const findLibraryItemByUrl = async (
|
|||
url: string,
|
||||
userId: string
|
||||
): Promise<LibraryItem | null> => {
|
||||
return authTrx(async (tx) =>
|
||||
tx
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.leftJoinAndSelect('library_item.labels', 'labels')
|
||||
.leftJoinAndSelect('library_item.highlights', 'highlights')
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
.andWhere('library_item.url = :url', { url })
|
||||
.getOne()
|
||||
return authTrx(
|
||||
async (tx) =>
|
||||
tx
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.leftJoinAndSelect('library_item.labels', 'labels')
|
||||
.leftJoinAndSelect('library_item.highlights', 'highlights')
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
.andWhere('library_item.url = :url', { url })
|
||||
.getOne(),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
}
|
||||
|
||||
export const refreshLibraryItem = async (
|
||||
id: string,
|
||||
userId: string,
|
||||
pubsub = createPubSubClient()
|
||||
): Promise<LibraryItem> => {
|
||||
return updateLibraryItem(
|
||||
id,
|
||||
{
|
||||
state: LibraryItemState.Succeeded,
|
||||
savedAt: new Date(),
|
||||
archivedAt: null,
|
||||
},
|
||||
userId,
|
||||
pubsub
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -311,8 +331,10 @@ export const createLibraryItem = async (
|
|||
userId: string,
|
||||
pubsub = createPubSubClient()
|
||||
): Promise<LibraryItem> => {
|
||||
const newLibraryItem = await authTrx(async (tx) =>
|
||||
tx.withRepository(libraryItemRepository).save(libraryItem)
|
||||
const newLibraryItem = await authTrx(
|
||||
async (tx) => tx.withRepository(libraryItemRepository).save(libraryItem),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
|
||||
await pubsub.entityCreated<LibraryItem>(
|
||||
|
|
@ -337,3 +359,18 @@ export const findLibraryItemsByPrefix = async (
|
|||
.getMany()
|
||||
)
|
||||
}
|
||||
|
||||
export const countByCreatedAt = async (
|
||||
startDate = new Date(0),
|
||||
endDate = new Date()
|
||||
): Promise<number> => {
|
||||
return authTrx(async (tx) =>
|
||||
tx
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.where('library_item.created_at between :startDate and :endDate', {
|
||||
startDate,
|
||||
endDate,
|
||||
})
|
||||
.getCount()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import * as httpContext from 'express-http-context2'
|
||||
import { readFileSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { createPage } from '../elastic/pages'
|
||||
import { ArticleSavingRequestStatus, Page, PageContext } from '../elastic/types'
|
||||
import { PageType } from '../generated/graphql'
|
||||
import { createPubSubClient } from '../pubsub'
|
||||
import { DeepPartial } from 'typeorm'
|
||||
import { LibraryItem, LibraryItemType } from '../entity/library_item'
|
||||
import { ArticleSavingRequestStatus } from '../generated/graphql'
|
||||
import { generateSlug, stringToHash } from '../utils/helpers'
|
||||
import { logger } from '../utils/logger'
|
||||
import { createLibraryItem } from './library_item'
|
||||
|
||||
type PopularRead = {
|
||||
url: string
|
||||
|
|
@ -61,12 +61,6 @@ export const addPopularRead = async (
|
|||
userId: string,
|
||||
name: string
|
||||
): Promise<string | undefined> => {
|
||||
const ctx: PageContext = {
|
||||
pubsub: createPubSubClient(),
|
||||
refresh: true,
|
||||
uid: userId,
|
||||
}
|
||||
|
||||
const pr = popularRead(name)
|
||||
if (!pr) {
|
||||
return undefined
|
||||
|
|
@ -75,30 +69,25 @@ export const addPopularRead = async (
|
|||
const saveTime = new Date()
|
||||
const slug = generateSlug(pr.title)
|
||||
|
||||
const articleToSave: Page = {
|
||||
id: '',
|
||||
const articleToSave: DeepPartial<LibraryItem> = {
|
||||
slug: slug,
|
||||
userId: userId,
|
||||
content: pr.content,
|
||||
originalHtml: pr.originalHtml,
|
||||
readableContent: pr.content,
|
||||
originalContent: pr.originalHtml,
|
||||
description: pr.description,
|
||||
title: pr.title,
|
||||
author: pr.author,
|
||||
url: pr.url,
|
||||
pageType: PageType.Article,
|
||||
hash: stringToHash(pr.content),
|
||||
image: pr.previewImage,
|
||||
originalUrl: pr.url,
|
||||
itemType: LibraryItemType.Article,
|
||||
textContentHash: stringToHash(pr.content),
|
||||
thumbnail: pr.previewImage,
|
||||
publishedAt: pr.publishedAt,
|
||||
savedAt: saveTime,
|
||||
createdAt: saveTime,
|
||||
siteName: pr.siteName,
|
||||
readingProgressPercent: 0,
|
||||
readingProgressAnchorIndex: 0,
|
||||
state: ArticleSavingRequestStatus.Succeeded,
|
||||
}
|
||||
|
||||
const pageId = await createPage(articleToSave, ctx)
|
||||
return pageId
|
||||
const item = await createLibraryItem(articleToSave, userId)
|
||||
return item.id
|
||||
}
|
||||
|
||||
const addPopularReads = async (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import { MulticastMessage } from 'firebase-admin/messaging'
|
||||
import { Page } from '../elastic/types'
|
||||
import { NewsletterEmail } from '../entity/newsletter_email'
|
||||
import { UserDeviceToken } from '../entity/user_device_tokens'
|
||||
import { env } from '../env'
|
||||
import { ContentReader } from '../generated/graphql'
|
||||
import { analytics } from '../utils/analytics'
|
||||
import { logger } from '../utils/logger'
|
||||
import { saveEmail, SaveEmailInput } from './save_email'
|
||||
|
|
@ -77,42 +73,42 @@ export const saveNewsletter = async (
|
|||
return true
|
||||
}
|
||||
|
||||
const messageForLink = (
|
||||
link: Page,
|
||||
deviceTokens: UserDeviceToken[]
|
||||
): MulticastMessage => {
|
||||
let title = '📫 - An article was added to your Omnivore Inbox'
|
||||
// const messageForLink = (
|
||||
// link: Page,
|
||||
// deviceTokens: UserDeviceToken[]
|
||||
// ): MulticastMessage => {
|
||||
// let title = '📫 - An article was added to your Omnivore Inbox'
|
||||
|
||||
if (link.author) {
|
||||
title = `📫 - ${link.author} has published a new article`
|
||||
}
|
||||
// if (link.author) {
|
||||
// title = `📫 - ${link.author} has published a new article`
|
||||
// }
|
||||
|
||||
const pushData = !link
|
||||
? undefined
|
||||
: {
|
||||
link: Buffer.from(
|
||||
JSON.stringify({
|
||||
id: link.id,
|
||||
url: link.url,
|
||||
slug: link.slug,
|
||||
title: link.title,
|
||||
image: link.image,
|
||||
author: link.author,
|
||||
isArchived: !!link.archivedAt,
|
||||
contentReader: ContentReader.Web,
|
||||
readingProgressPercent: link.readingProgressPercent,
|
||||
readingProgressAnchorIndex: link.readingProgressAnchorIndex,
|
||||
})
|
||||
).toString('base64'),
|
||||
}
|
||||
// const pushData = !link
|
||||
// ? undefined
|
||||
// : {
|
||||
// link: Buffer.from(
|
||||
// JSON.stringify({
|
||||
// id: link.id,
|
||||
// url: link.url,
|
||||
// slug: link.slug,
|
||||
// title: link.title,
|
||||
// image: link.image,
|
||||
// author: link.author,
|
||||
// isArchived: !!link.archivedAt,
|
||||
// contentReader: ContentReader.Web,
|
||||
// readingProgressPercent: link.readingProgressPercent,
|
||||
// readingProgressAnchorIndex: link.readingProgressAnchorIndex,
|
||||
// })
|
||||
// ).toString('base64'),
|
||||
// }
|
||||
|
||||
return {
|
||||
notification: {
|
||||
title: title,
|
||||
body: link.title,
|
||||
imageUrl: link.image || undefined,
|
||||
},
|
||||
data: pushData,
|
||||
tokens: deviceTokens.map((token) => token.token),
|
||||
}
|
||||
}
|
||||
// return {
|
||||
// notification: {
|
||||
// title: title,
|
||||
// body: link.title,
|
||||
// imageUrl: link.image || undefined,
|
||||
// },
|
||||
// data: pushData,
|
||||
// tokens: deviceTokens.map((token) => token.token),
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { UploadFile } from '../entity/upload_file'
|
||||
import { authTrx } from '../repository'
|
||||
import { authTrx, getRepository } from '../repository'
|
||||
|
||||
export const findUploadFileById = async (id: string) => {
|
||||
return authTrx(async (tx) => tx.getRepository(UploadFile).findOneBy({ id }))
|
||||
return getRepository(UploadFile).findOneBy({ id })
|
||||
}
|
||||
|
||||
export const setFileUploadComplete = async (id: string) => {
|
||||
return authTrx(async (tx) =>
|
||||
tx.getRepository(UploadFile).save({ id, status: 'COMPLETED' })
|
||||
export const setFileUploadComplete = async (id: string, userId?: string) => {
|
||||
return authTrx(
|
||||
async (tx) =>
|
||||
tx.getRepository(UploadFile).save({ id, status: 'COMPLETED' }),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { promisify } from 'util'
|
|||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ApiKey } from '../entity/api_key'
|
||||
import { env } from '../env'
|
||||
import { authTrx } from '../repository'
|
||||
import { getRepository } from '../repository'
|
||||
import { Claims, ClaimsToSet } from '../resolvers/types'
|
||||
import { logger } from './logger'
|
||||
|
||||
|
|
@ -33,39 +33,33 @@ export const hashApiKey = (apiKey: string) => {
|
|||
|
||||
export const claimsFromApiKey = async (key: string): Promise<Claims> => {
|
||||
const hashedKey = hashApiKey(key)
|
||||
return authTrx(
|
||||
async (tx) => {
|
||||
const apiKeyRepo = tx.getRepository(ApiKey)
|
||||
|
||||
const apiKey = await apiKeyRepo.findOne({
|
||||
where: {
|
||||
key: hashedKey,
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!apiKey) {
|
||||
throw new Error('api key not found')
|
||||
}
|
||||
const apiKeyRepo = getRepository(ApiKey)
|
||||
|
||||
const iat = Math.floor(Date.now() / 1000)
|
||||
const exp = Math.floor(new Date(apiKey.expiresAt).getTime() / 1000)
|
||||
if (exp < iat) {
|
||||
throw new Error('api key expired')
|
||||
}
|
||||
|
||||
// update last used
|
||||
await apiKeyRepo.update(apiKey.id, { usedAt: new Date() })
|
||||
|
||||
return {
|
||||
uid: apiKey.user.id,
|
||||
iat,
|
||||
exp,
|
||||
}
|
||||
const apiKey = await apiKeyRepo.findOne({
|
||||
where: {
|
||||
key: hashedKey,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
'omnivore_admin'
|
||||
)
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!apiKey) {
|
||||
throw new Error('api key not found')
|
||||
}
|
||||
|
||||
const iat = Math.floor(Date.now() / 1000)
|
||||
const exp = Math.floor(new Date(apiKey.expiresAt).getTime() / 1000)
|
||||
if (exp < iat) {
|
||||
throw new Error('api key expired')
|
||||
}
|
||||
|
||||
// update last used
|
||||
await apiKeyRepo.update(apiKey.id, { usedAt: new Date() })
|
||||
|
||||
return {
|
||||
uid: apiKey.user.id,
|
||||
iat,
|
||||
exp,
|
||||
}
|
||||
}
|
||||
|
||||
// verify jwt token first
|
||||
|
|
|
|||
|
|
@ -102,10 +102,9 @@ CREATE TRIGGER library_item_tsv_update BEFORE INSERT OR UPDATE
|
|||
|
||||
ALTER TABLE omnivore.library_item ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY select_library_item ON omnivore.library_item FOR SELECT USING (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY insert_library_item ON omnivore.library_item FOR INSERT WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY update_library_item ON omnivore.library_item FOR UPDATE USING (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY delete_library_item ON omnivore.library_item FOR DELETE USING (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY library_item_policy ON omnivore.library_item
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.library_item TO omnivore_user;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ CREATE TABLE omnivore.entity_labels (
|
|||
label_id uuid NOT NULL REFERENCES omnivore.labels(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT SELECT, INSERT, DELETE ON omnivore.entity_labels TO omnivore_user;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_library_item_labels()
|
||||
RETURNS trigger AS $$
|
||||
DECLARE
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ CREATE TABLE omnivore.library_item_preview (
|
|||
updated_at timestamptz NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
GRANT SELECT, INSERT ON omnivore.library_item_preview TO omnivore_user;
|
||||
|
||||
CREATE TRIGGER update_library_item_preview_modtime BEFORE UPDATE ON omnivore.library_item_preview FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
||||
|
||||
COMMIT;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ ALTER TABLE omnivore.highlight
|
|||
ADD COLUMN html text,
|
||||
ALTER COLUMN quote DROP NOT NULL,
|
||||
ALTER COLUMN patch DROP NOT NULL,
|
||||
ALTER COLUMN highlight_position_percent DROP NOT NULL,
|
||||
ALTER COLUMN highlight_position_anchor_index DROP NOT NULL,
|
||||
DROP COLUMN article_id,
|
||||
DROP COLUMN elastic_page_id;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,6 @@ CREATE TABLE omnivore.recommendation (
|
|||
created_at timestamptz NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
GRANT SELECT, INSERT ON omnivore.library_item TO omnivore_user;
|
||||
|
||||
COMMIT;
|
||||
|
|
|
|||
|
|
@ -4,257 +4,57 @@
|
|||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.api_key ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_api_key on omnivore.api_key
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_api_key on omnivore.api_key
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_api_key on omnivore.api_key
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_api_key on omnivore.api_key
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.api_key TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.features ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_features on omnivore.features
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_features on omnivore.features
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_features on omnivore.features
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_features on omnivore.features
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.features TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.filters ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_filters on omnivore.filters
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_filters on omnivore.filters
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_filters on omnivore.filters
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_filters on omnivore.filters
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.filters TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.integrations ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_integrations on omnivore.integrations
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_integrations on omnivore.integrations
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_integrations on omnivore.integrations
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_integrations on omnivore.integrations
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.integrations TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.newsletter_emails ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_newsletter_emails on omnivore.newsletter_emails
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_newsletter_emails on omnivore.newsletter_emails
|
||||
FOR INSERT TO omnivore_user
|
||||
CREATE POLICY features_policy on omnivore.features
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_newsletter_emails on omnivore.newsletter_emails
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
ALTER TABLE omnivore.filters ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY filters_policy on omnivore.filters
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, DELETE ON omnivore.newsletter_emails TO omnivore_user;
|
||||
ALTER TABLE omnivore.integrations ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY integrations_policy on omnivore.integrations
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER POLICY read_labels on omnivore.labels
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
ALTER TABLE omnivore.newsletter_emails ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY newsletter_emails_policy on omnivore.newsletter_emails
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER POLICY create_labels on omnivore.labels
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY labels_policy on omnivore.labels
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.received_emails ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_received_emails on omnivore.received_emails
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_received_emails on omnivore.received_emails
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_received_emails on omnivore.received_emails
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_received_emails on omnivore.received_emails
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.received_emails TO omnivore_user;
|
||||
CREATE POLICY received_emails_policy on omnivore.received_emails
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_rules on omnivore.rules
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_rules on omnivore.rules
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_rules on omnivore.rules
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_rules on omnivore.rules
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.rules TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.subscriptions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_subscriptions on omnivore.subscriptions
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_subscriptions on omnivore.subscriptions
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_subscriptions on omnivore.subscriptions
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_subscriptions on omnivore.subscriptions
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.subscriptions TO omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.upload_files ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_upload_files on omnivore.upload_files
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_upload_files on omnivore.upload_files
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_upload_files on omnivore.upload_files
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_upload_files on omnivore.upload_files
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.upload_files TO omnivore_user;
|
||||
CREATE POLICY rules_policy on omnivore.rules
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.webhooks ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY webhooks_policy on omnivore.webhooks
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY read_webhooks on omnivore.webhooks
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_webhooks on omnivore.webhooks
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_webhooks on omnivore.webhooks
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_webhooks on omnivore.webhooks
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.webhooks TO omnivore_user;
|
||||
|
||||
ALTER POLICY read_user_device_tokens on omnivore.user_device_tokens
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER POLICY create_user_device_tokens on omnivore.user_device_tokens
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
CREATE POLICY user_device_tokens_policy on omnivore.user_device_tokens
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.search_history ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY search_history_policy on omnivore.search_history
|
||||
USING (user_id = omnivore.get_current_user_id())
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY read_search_history on omnivore.search_history
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_search_history on omnivore.search_history
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_search_history on omnivore.search_history
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_search_history on omnivore.search_history
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.group_membership ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY read_group_membership on omnivore.group_membership
|
||||
FOR SELECT TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY create_group_membership on omnivore.group_membership
|
||||
FOR INSERT TO omnivore_user
|
||||
WITH CHECK (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY update_group_membership on omnivore.group_membership
|
||||
FOR UPDATE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
CREATE POLICY delete_group_membership on omnivore.group_membership
|
||||
FOR DELETE TO omnivore_user
|
||||
USING (user_id = omnivore.get_current_user_id());
|
||||
|
||||
ALTER TABLE omnivore.abuse_report
|
||||
ALTER COLUMN elastic_page_id RENAME TO library_item_id,
|
||||
DROP COLUMN page_id;
|
||||
ALTER TABLE omnivore.content_display_report
|
||||
ALTER COLUMN elastic_page_id RENAME TO library_item_id,
|
||||
DROP COLUMN page_id;
|
||||
ALTER TABLE omnivore.abuse_report DROP COLUMN page_id;
|
||||
ALTER TABLE omnivore.abuse_report RENAME COLUMN elastic_page_id TO library_item_id;
|
||||
ALTER TABLE omnivore.content_display_report DROP COLUMN page_id;
|
||||
ALTER TABLE omnivore.content_display_report RENAME COLUMN elastic_page_id TO library_item_id;
|
||||
|
||||
COMMIT;
|
||||
|
|
|
|||
|
|
@ -4,97 +4,37 @@
|
|||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.api_key DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_api_key on omnivore.api_key;
|
||||
DROP POLICY create_api_key on omnivore.api_key;
|
||||
DROP POLICY update_api_key on omnivore.api_key;
|
||||
DROP POLICY delete_api_key on omnivore.api_key;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.api_key FROM omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.features DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_features on omnivore.features;
|
||||
DROP POLICY create_features on omnivore.features;
|
||||
DROP POLICY update_features on omnivore.features;
|
||||
DROP POLICY delete_features on omnivore.features;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.features FROM omnivore_user;
|
||||
DROP POLICY features_policy on omnivore.features;
|
||||
|
||||
ALTER TABLE omnivore.filters DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_filters on omnivore.filters;
|
||||
DROP POLICY create_filters on omnivore.filters;
|
||||
DROP POLICY update_filters on omnivore.filters;
|
||||
DROP POLICY delete_filters on omnivore.filters;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.filters FROM omnivore_user;
|
||||
DROP POLICY filters_policy on omnivore.filters;
|
||||
|
||||
ALTER TABLE omnivore.integrations DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_integrations on omnivore.integrations;
|
||||
DROP POLICY create_integrations on omnivore.integrations;
|
||||
DROP POLICY update_integrations on omnivore.integrations;
|
||||
DROP POLICY delete_integrations on omnivore.integrations;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.integrations FROM omnivore_user;
|
||||
DROP POLICY integrations_policy on omnivore.integrations;
|
||||
|
||||
ALTER TABLE omnivore.newsletter_emails DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_newsletter_emails on omnivore.newsletter_emails;
|
||||
DROP POLICY create_newsletter_emails on omnivore.newsletter_emails;
|
||||
DROP POLICY delete_newsletter_emails on omnivore.newsletter_emails;
|
||||
REVOKE SELECT, INSERT, DELETE ON omnivore.newsletter_emails FROM omnivore_user;
|
||||
DROP POLICY newsletter_emails_policy on omnivore.newsletter_emails;
|
||||
|
||||
ALTER POLICY read_labels on omnivore.labels USING (true);
|
||||
ALTER POLICY create_labels on omnivore.labels WITH CHECK (true);
|
||||
DROP POLICY labels_policy on omnivore.labels;
|
||||
|
||||
ALTER TABLE omnivore.received_emails DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_received_emails on omnivore.received_emails;
|
||||
DROP POLICY create_received_emails on omnivore.received_emails;
|
||||
DROP POLICY update_received_emails on omnivore.received_emails;
|
||||
DROP POLICY delete_received_emails on omnivore.received_emails;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.received_emails FROM omnivore_user;
|
||||
DROP POLICY received_emails_policy on omnivore.received_emails;
|
||||
|
||||
ALTER TABLE omnivore.rules DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_rules on omnivore.rules;
|
||||
DROP POLICY create_rules on omnivore.rules;
|
||||
DROP POLICY update_rules on omnivore.rules;
|
||||
DROP POLICY delete_rules on omnivore.rules;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.rules FROM omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.subscriptions DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_subscriptions on omnivore.subscriptions;
|
||||
DROP POLICY create_subscriptions on omnivore.subscriptions;
|
||||
DROP POLICY update_subscriptions on omnivore.subscriptions;
|
||||
DROP POLICY delete_subscriptions on omnivore.subscriptions;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.subscriptions FROM omnivore_user;
|
||||
|
||||
ALTER TABLE omnivore.upload_files DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_upload_files on omnivore.upload_files;
|
||||
DROP POLICY create_upload_files on omnivore.upload_files;
|
||||
DROP POLICY update_upload_files on omnivore.upload_files;
|
||||
DROP POLICY delete_upload_files on omnivore.upload_files;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.upload_files FROM omnivore_user;
|
||||
DROP POLICY rules_policy on omnivore.rules;
|
||||
|
||||
ALTER TABLE omnivore.webhooks DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_webhooks on omnivore.webhooks;
|
||||
DROP POLICY create_webhooks on omnivore.webhooks;
|
||||
DROP POLICY update_webhooks on omnivore.webhooks;
|
||||
DROP POLICY delete_webhooks on omnivore.webhooks;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON omnivore.webhooks FROM omnivore_user;
|
||||
DROP POLICY webhooks_policy on omnivore.webhooks;
|
||||
|
||||
ALTER POLICY read_user_device_tokens on omnivore.user_device_tokens
|
||||
FOR SELECT FROM omnivore_user
|
||||
USING (true);
|
||||
|
||||
ALTER POLICY create_user_device_tokens on omnivore.user_device_tokens
|
||||
FOR INSERT FROM omnivore_user
|
||||
WITH CHECK (true);
|
||||
DROP POLICY user_device_tokens_policy on omnivore.user_device_tokens;
|
||||
|
||||
ALTER TABLE omnivore.search_history DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY read_search_history on omnivore.search_history;
|
||||
DROP POLICY create_search_history on omnivore.search_history;
|
||||
DROP POLICY update_search_history on omnivore.search_history;
|
||||
DROP POLICY delete_search_history on omnivore.search_history;
|
||||
DROP POLICY search_history_policy on omnivore.search_history;
|
||||
|
||||
ALTER TABLE omnivore.abuse_report
|
||||
ALTER COLUMN library_item_id RENAME TO elastic_page_id,
|
||||
ADD COLUMN page_id text;
|
||||
ALTER TABLE omnivore.content_display_report
|
||||
ALTER COLUMN library_item_id RENAME TO elastic_page_id,
|
||||
ADD COLUMN page_id text;
|
||||
ALTER TABLE omnivore.abuse_report RENAME COLUMN library_item_id TO elastic_page_id;
|
||||
ALTER TABLE omnivore.abuse_report ADD COLUMN page_id text;
|
||||
ALTER TABLE omnivore.content_display_report RENAME COLUMN library_item_id TO elastic_page_id;
|
||||
ALTER TABLE omnivore.content_display_report ADD COLUMN page_id text;
|
||||
|
||||
COMMIT;
|
||||
|
|
|
|||
Loading…
Reference in a new issue