Feature/subscription list resolver (#432)
* add subscriptions table * add listSubscriptions schema * add listSubscriptions resolver
This commit is contained in:
48
packages/api/src/entity/subscription.ts
Normal file
48
packages/api/src/entity/subscription.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
import { User } from './user'
|
||||
import { SubscriptionStatus } from '../generated/graphql'
|
||||
|
||||
@Entity({ name: 'subscriptions' })
|
||||
export class Subscription {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User
|
||||
|
||||
@Column('text')
|
||||
name!: string
|
||||
|
||||
@Column('enum', {
|
||||
enum: SubscriptionStatus,
|
||||
default: SubscriptionStatus.Active,
|
||||
})
|
||||
status!: SubscriptionStatus
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
description?: string
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
url?: string
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
unsubscribeMailTo?: string
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
unsubscribeHttpUrl?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date
|
||||
}
|
||||
@ -11,6 +11,7 @@ import { MembershipTier, RegistrationType } from '../datalayer/user/model'
|
||||
import { NewsletterEmail } from './newsletter_email'
|
||||
import { Profile } from './profile'
|
||||
import { Label } from './label'
|
||||
import { Subscription } from './subscription'
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@ -49,4 +50,7 @@ export class User {
|
||||
|
||||
@OneToMany(() => Label, (label) => label.user)
|
||||
labels?: Label[]
|
||||
|
||||
@OneToMany(() => Subscription, (subscription) => subscription.user)
|
||||
subscriptions?: Subscription[]
|
||||
}
|
||||
|
||||
@ -1122,6 +1122,7 @@ export type Query = {
|
||||
reminder: ReminderResult;
|
||||
search: SearchResult;
|
||||
sharedArticle: SharedArticleResult;
|
||||
subscriptions: SubscriptionsResult;
|
||||
user: UserResult;
|
||||
users: UsersResult;
|
||||
validateUsername: Scalars['Boolean'];
|
||||
@ -1614,6 +1615,42 @@ export type SortParams = {
|
||||
order?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export type Subscription = {
|
||||
__typename?: 'Subscription';
|
||||
createdAt: Scalars['Date'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
status: SubscriptionStatus;
|
||||
unsubscribeHttpUrl?: Maybe<Scalars['String']>;
|
||||
unsubscribeMailTo?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['Date'];
|
||||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type SubscriptionsError = {
|
||||
__typename?: 'SubscriptionsError';
|
||||
errorCodes: Array<SubscriptionsErrorCode>;
|
||||
};
|
||||
|
||||
export enum SubscriptionsErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type SubscriptionsResult = SubscriptionsError | SubscriptionsSuccess;
|
||||
|
||||
export type SubscriptionsSuccess = {
|
||||
__typename?: 'SubscriptionsSuccess';
|
||||
subscriptions: Array<Subscription>;
|
||||
};
|
||||
|
||||
export enum SubscriptionStatus {
|
||||
Active = 'ACTIVE',
|
||||
Deleted = 'DELETED',
|
||||
Unsubscribed = 'UNSUBSCRIBED'
|
||||
}
|
||||
|
||||
export type UpdateHighlightError = {
|
||||
__typename?: 'UpdateHighlightError';
|
||||
errorCodes: Array<UpdateHighlightErrorCode>;
|
||||
@ -2206,6 +2243,12 @@ export type ResolversTypes = {
|
||||
SortOrder: SortOrder;
|
||||
SortParams: SortParams;
|
||||
String: ResolverTypeWrapper<Scalars['String']>;
|
||||
Subscription: ResolverTypeWrapper<{}>;
|
||||
SubscriptionsError: ResolverTypeWrapper<SubscriptionsError>;
|
||||
SubscriptionsErrorCode: SubscriptionsErrorCode;
|
||||
SubscriptionsResult: ResolversTypes['SubscriptionsError'] | ResolversTypes['SubscriptionsSuccess'];
|
||||
SubscriptionsSuccess: ResolverTypeWrapper<SubscriptionsSuccess>;
|
||||
SubscriptionStatus: SubscriptionStatus;
|
||||
UpdateHighlightError: ResolverTypeWrapper<UpdateHighlightError>;
|
||||
UpdateHighlightErrorCode: UpdateHighlightErrorCode;
|
||||
UpdateHighlightInput: UpdateHighlightInput;
|
||||
@ -2452,6 +2495,10 @@ export type ResolversParentTypes = {
|
||||
SignupSuccess: SignupSuccess;
|
||||
SortParams: SortParams;
|
||||
String: Scalars['String'];
|
||||
Subscription: {};
|
||||
SubscriptionsError: SubscriptionsError;
|
||||
SubscriptionsResult: ResolversParentTypes['SubscriptionsError'] | ResolversParentTypes['SubscriptionsSuccess'];
|
||||
SubscriptionsSuccess: SubscriptionsSuccess;
|
||||
UpdateHighlightError: UpdateHighlightError;
|
||||
UpdateHighlightInput: UpdateHighlightInput;
|
||||
UpdateHighlightReplyError: UpdateHighlightReplyError;
|
||||
@ -3170,6 +3217,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
|
||||
reminder?: Resolver<ResolversTypes['ReminderResult'], ParentType, ContextType, RequireFields<QueryReminderArgs, 'linkId'>>;
|
||||
search?: Resolver<ResolversTypes['SearchResult'], ParentType, ContextType, Partial<QuerySearchArgs>>;
|
||||
sharedArticle?: Resolver<ResolversTypes['SharedArticleResult'], ParentType, ContextType, RequireFields<QuerySharedArticleArgs, 'slug' | 'username'>>;
|
||||
subscriptions?: Resolver<ResolversTypes['SubscriptionsResult'], ParentType, ContextType>;
|
||||
user?: Resolver<ResolversTypes['UserResult'], ParentType, ContextType, Partial<QueryUserArgs>>;
|
||||
users?: Resolver<ResolversTypes['UsersResult'], ParentType, ContextType>;
|
||||
validateUsername?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<QueryValidateUsernameArgs, 'username'>>;
|
||||
@ -3431,6 +3479,32 @@ export type SignupSuccessResolvers<ContextType = ResolverContext, ParentType ext
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Subscription'] = ResolversParentTypes['Subscription']> = {
|
||||
createdAt?: SubscriptionResolver<ResolversTypes['Date'], "createdAt", ParentType, ContextType>;
|
||||
description?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "description", ParentType, ContextType>;
|
||||
id?: SubscriptionResolver<ResolversTypes['ID'], "id", ParentType, ContextType>;
|
||||
name?: SubscriptionResolver<ResolversTypes['String'], "name", ParentType, ContextType>;
|
||||
status?: SubscriptionResolver<ResolversTypes['SubscriptionStatus'], "status", ParentType, ContextType>;
|
||||
unsubscribeHttpUrl?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "unsubscribeHttpUrl", ParentType, ContextType>;
|
||||
unsubscribeMailTo?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "unsubscribeMailTo", ParentType, ContextType>;
|
||||
updatedAt?: SubscriptionResolver<ResolversTypes['Date'], "updatedAt", ParentType, ContextType>;
|
||||
url?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "url", ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionsErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SubscriptionsError'] = ResolversParentTypes['SubscriptionsError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['SubscriptionsErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionsResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SubscriptionsResult'] = ResolversParentTypes['SubscriptionsResult']> = {
|
||||
__resolveType: TypeResolveFn<'SubscriptionsError' | 'SubscriptionsSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionsSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SubscriptionsSuccess'] = ResolversParentTypes['SubscriptionsSuccess']> = {
|
||||
subscriptions?: Resolver<Array<ResolversTypes['Subscription']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type UpdateHighlightErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateHighlightError'] = ResolversParentTypes['UpdateHighlightError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['UpdateHighlightErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@ -3769,6 +3843,10 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
SignupError?: SignupErrorResolvers<ContextType>;
|
||||
SignupResult?: SignupResultResolvers<ContextType>;
|
||||
SignupSuccess?: SignupSuccessResolvers<ContextType>;
|
||||
Subscription?: SubscriptionResolvers<ContextType>;
|
||||
SubscriptionsError?: SubscriptionsErrorResolvers<ContextType>;
|
||||
SubscriptionsResult?: SubscriptionsResultResolvers<ContextType>;
|
||||
SubscriptionsSuccess?: SubscriptionsSuccessResolvers<ContextType>;
|
||||
UpdateHighlightError?: UpdateHighlightErrorResolvers<ContextType>;
|
||||
UpdateHighlightReplyError?: UpdateHighlightReplyErrorResolvers<ContextType>;
|
||||
UpdateHighlightReplyResult?: UpdateHighlightReplyResultResolvers<ContextType>;
|
||||
|
||||
@ -821,6 +821,7 @@ type Query {
|
||||
reminder(linkId: ID!): ReminderResult!
|
||||
search(after: String, first: Int, query: String): SearchResult!
|
||||
sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult!
|
||||
subscriptions: SubscriptionsResult!
|
||||
user(userId: ID, username: String): UserResult!
|
||||
users: UsersResult!
|
||||
validateUsername(username: String!): Boolean!
|
||||
@ -1212,6 +1213,39 @@ input SortParams {
|
||||
order: SortOrder
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
createdAt: Date!
|
||||
description: String
|
||||
id: ID!
|
||||
name: String!
|
||||
status: SubscriptionStatus!
|
||||
unsubscribeHttpUrl: String
|
||||
unsubscribeMailTo: String
|
||||
updatedAt: Date!
|
||||
url: String
|
||||
}
|
||||
|
||||
type SubscriptionsError {
|
||||
errorCodes: [SubscriptionsErrorCode!]!
|
||||
}
|
||||
|
||||
enum SubscriptionsErrorCode {
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union SubscriptionsResult = SubscriptionsError | SubscriptionsSuccess
|
||||
|
||||
type SubscriptionsSuccess {
|
||||
subscriptions: [Subscription!]!
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ACTIVE
|
||||
DELETED
|
||||
UNSUBSCRIBED
|
||||
}
|
||||
|
||||
type UpdateHighlightError {
|
||||
errorCodes: [UpdateHighlightErrorCode!]!
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ import {
|
||||
setUserPersonalizationResolver,
|
||||
signupResolver,
|
||||
updateHighlightResolver,
|
||||
updateLabelResolver,
|
||||
updateLinkShareInfoResolver,
|
||||
updateReminderResolver,
|
||||
updateSharedCommentResolver,
|
||||
@ -73,7 +74,6 @@ import {
|
||||
updateUserResolver,
|
||||
uploadFileRequestResolver,
|
||||
validateUsernameResolver,
|
||||
updateLabelResolver,
|
||||
} from './index'
|
||||
import { getShareInfoForArticle } from '../datalayer/links/share_info'
|
||||
import {
|
||||
@ -82,6 +82,7 @@ import {
|
||||
} from '../utils/uploads'
|
||||
import { getPageByParam } from '../elastic/pages'
|
||||
import { generateApiKeyResolver } from './api_key'
|
||||
import { subscriptionsResolver } from './subscriptions'
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
type ResultResolveType = {
|
||||
@ -160,6 +161,7 @@ export const functionResolvers = {
|
||||
reminder: reminderResolver,
|
||||
labels: labelsResolver,
|
||||
search: searchResolver,
|
||||
subscriptions: subscriptionsResolver,
|
||||
},
|
||||
User: {
|
||||
async sharedArticles(
|
||||
@ -547,4 +549,5 @@ export const functionResolvers = {
|
||||
...resultResolveTypeResolver('SetLabels'),
|
||||
...resultResolveTypeResolver('GenerateApiKey'),
|
||||
...resultResolveTypeResolver('Search'),
|
||||
...resultResolveTypeResolver('Subscriptions'),
|
||||
}
|
||||
|
||||
54
packages/api/src/resolvers/subscriptions/index.ts
Normal file
54
packages/api/src/resolvers/subscriptions/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import {
|
||||
SubscriptionsError,
|
||||
SubscriptionsErrorCode,
|
||||
SubscriptionsSuccess,
|
||||
SubscriptionStatus,
|
||||
} from '../../generated/graphql'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { env } from '../../env'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
import { User } from '../../entity/user'
|
||||
|
||||
export const subscriptionsResolver = authorized<
|
||||
SubscriptionsSuccess,
|
||||
SubscriptionsError
|
||||
>(async (_obj, _params, { claims: { uid }, log }) => {
|
||||
log.info('subscriptionsResolver')
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'subscriptions',
|
||||
properties: {
|
||||
env: env.server.apiEnv,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOne({
|
||||
where: { id: uid, subscriptions: { status: SubscriptionStatus.Active } },
|
||||
relations: {
|
||||
subscriptions: true,
|
||||
},
|
||||
order: {
|
||||
subscriptions: {
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [SubscriptionsErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscriptions: user.subscriptions || [],
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
return {
|
||||
errorCodes: [SubscriptionsErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1444,6 +1444,39 @@ const schema = gql`
|
||||
errorCodes: [SearchErrorCode!]!
|
||||
}
|
||||
|
||||
union SubscriptionsResult = SubscriptionsSuccess | SubscriptionsError
|
||||
|
||||
type SubscriptionsSuccess {
|
||||
subscriptions: [Subscription!]!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
id: ID!
|
||||
name: String!
|
||||
url: String
|
||||
description: String
|
||||
status: SubscriptionStatus!
|
||||
unsubscribeMailTo: String
|
||||
unsubscribeHttpUrl: String
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ACTIVE
|
||||
UNSUBSCRIBED
|
||||
DELETED
|
||||
}
|
||||
|
||||
type SubscriptionsError {
|
||||
errorCodes: [SubscriptionsErrorCode!]!
|
||||
}
|
||||
|
||||
enum SubscriptionsErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
@ -1542,6 +1575,7 @@ const schema = gql`
|
||||
reminder(linkId: ID!): ReminderResult!
|
||||
labels: LabelsResult!
|
||||
search(after: String, first: Int, query: String): SearchResult!
|
||||
subscriptions: SubscriptionsResult!
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import Postgrator from "postgrator";
|
||||
import { User } from "../src/entity/user";
|
||||
import { Profile } from "../src/entity/profile";
|
||||
import { Page } from "../src/entity/page";
|
||||
import { Link } from "../src/entity/link";
|
||||
import { Reminder } from "../src/entity/reminder";
|
||||
import { NewsletterEmail } from "../src/entity/newsletter_email";
|
||||
import { UserDeviceToken } from "../src/entity/user_device_tokens";
|
||||
import { Label } from "../src/entity/label";
|
||||
import { AppDataSource } from "../src/server";
|
||||
import { getRepository } from "../src/entity/utils";
|
||||
import { createUser } from "../src/services/create_user";
|
||||
import { SnakeNamingStrategy } from "typeorm-naming-strategies";
|
||||
import Postgrator from 'postgrator'
|
||||
import { User } from '../src/entity/user'
|
||||
import { Profile } from '../src/entity/profile'
|
||||
import { Page } from '../src/entity/page'
|
||||
import { Link } from '../src/entity/link'
|
||||
import { Reminder } from '../src/entity/reminder'
|
||||
import { NewsletterEmail } from '../src/entity/newsletter_email'
|
||||
import { UserDeviceToken } from '../src/entity/user_device_tokens'
|
||||
import { Label } from '../src/entity/label'
|
||||
import { Subscription } from '../src/entity/subscription'
|
||||
import { AppDataSource } from '../src/server'
|
||||
import { getRepository } from '../src/entity/utils'
|
||||
import { createUser } from '../src/services/create_user'
|
||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
|
||||
import { SubscriptionStatus } from '../src/generated/graphql'
|
||||
|
||||
const runMigrations = async () => {
|
||||
const migrationDirectory = __dirname + '/../../db/migrations'
|
||||
@ -187,3 +189,14 @@ export const createTestLabel = async (
|
||||
color: color,
|
||||
})
|
||||
}
|
||||
|
||||
export const createTestSubscription = async (
|
||||
user: User,
|
||||
name: string
|
||||
): Promise<Subscription> => {
|
||||
return getRepository(Subscription).save({
|
||||
user,
|
||||
name,
|
||||
status: SubscriptionStatus.Active,
|
||||
})
|
||||
}
|
||||
|
||||
81
packages/api/test/resolvers/subscriptions.test.ts
Normal file
81
packages/api/test/resolvers/subscriptions.test.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { createTestSubscription, createTestUser, deleteTestUser } from '../db'
|
||||
import { graphqlRequest, request } from '../util'
|
||||
import { Subscription } from '../../src/entity/subscription'
|
||||
import { expect } from 'chai'
|
||||
import 'mocha'
|
||||
import { User } from '../../src/entity/user'
|
||||
|
||||
describe('Subscriptions API', () => {
|
||||
const username = 'fakeUser'
|
||||
|
||||
let user: User
|
||||
let authToken: string
|
||||
let subscriptions: Subscription[]
|
||||
|
||||
before(async () => {
|
||||
// create test user and login
|
||||
user = await createTestUser(username)
|
||||
const res = await request
|
||||
.post('/local/debug/fake-user-login')
|
||||
.send({ fakeEmail: user.email })
|
||||
|
||||
authToken = res.body.authToken
|
||||
|
||||
// create testing subscriptions
|
||||
const sub1 = await createTestSubscription(user, 'sub_1')
|
||||
const sub2 = await createTestSubscription(user, 'sub_2')
|
||||
subscriptions = [sub2, sub1]
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// clean up
|
||||
await deleteTestUser(username)
|
||||
})
|
||||
|
||||
describe('GET subscriptions', () => {
|
||||
let query: string
|
||||
|
||||
beforeEach(() => {
|
||||
query = `
|
||||
query {
|
||||
subscriptions {
|
||||
... on SubscriptionsSuccess {
|
||||
subscriptions {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
... on SubscriptionsError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
it('should return subscriptions', async () => {
|
||||
const res = await graphqlRequest(query, authToken).expect(200)
|
||||
|
||||
expect(res.body.data.subscriptions.subscriptions).to.eql(
|
||||
subscriptions.map((sub) => ({
|
||||
id: sub.id,
|
||||
name: sub.name,
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
it('responds status code 400 when invalid query', async () => {
|
||||
const invalidQuery = `
|
||||
query {
|
||||
subscriptions {}
|
||||
}
|
||||
`
|
||||
return graphqlRequest(invalidQuery, authToken).expect(400)
|
||||
})
|
||||
|
||||
it('responds status code 500 when invalid user', async () => {
|
||||
const invalidAuthToken = 'Fake token'
|
||||
return graphqlRequest(query, invalidAuthToken).expect(500)
|
||||
})
|
||||
})
|
||||
})
|
||||
27
packages/db/migrations/0080.do.add_subscriptions_table.sql
Executable file
27
packages/db/migrations/0080.do.add_subscriptions_table.sql
Executable file
@ -0,0 +1,27 @@
|
||||
-- Type: DO
|
||||
-- Name: add_subscriptions_table
|
||||
-- Description: Add subscriptions table
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TYPE subscription_status_type AS ENUM ('ACTIVE', 'UNSUBSCRIBED', 'DELETED');
|
||||
|
||||
CREATE TABLE omnivore.subscriptions (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
|
||||
user_id uuid NOT NULL REFERENCES omnivore.user (id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
description text,
|
||||
url text,
|
||||
status subscription_status_type NOT NULL,
|
||||
unsubscribe_mail_to text,
|
||||
unsubscribe_http_url text,
|
||||
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
updated_at timestamptz NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_subscription_modtime BEFORE UPDATE ON omnivore.subscriptions
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE ON omnivore.subscriptions TO omnivore_user;
|
||||
|
||||
COMMIT;
|
||||
10
packages/db/migrations/0080.undo.add_subscriptions_table.sql
Executable file
10
packages/db/migrations/0080.undo.add_subscriptions_table.sql
Executable file
@ -0,0 +1,10 @@
|
||||
-- Type: UNDO
|
||||
-- Name: add_subscriptions_table
|
||||
-- Description: Add subscriptions table
|
||||
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE omnivore.subscriptions;
|
||||
DROP TYPE subscription_status_type;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user