Skip to content

Commit

Permalink
Merge pull request #258 from maany/feature-255-feature_subscriptions
Browse files Browse the repository at this point in the history
add feature: Get Subscriptions
  • Loading branch information
maany authored Jul 18, 2023
2 parents 2564fd0 + 353204a commit cc51d88
Show file tree
Hide file tree
Showing 29 changed files with 666 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/app/subscription/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Code } from "@/component-library/components/Text/Content/Code";
export default function PageSubscription() {
return (
<PageSubscriptionStory
subscriptionMeta={createSubscriptionMeta()}
subscriptionViewModel={createSubscriptionMeta()}
editFilter={(filter: string) => { }}
editReplicationRules={(rules: string) => { }}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ const Template: StoryFn<typeof P> = (args) => <P {...args} />;

export const PageSubscription = Template.bind({});
PageSubscription.args = {
subscriptionMeta: createSubscriptionMeta()
subscriptionViewModel: createSubscriptionMeta()
};
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import { twMerge } from "tailwind-merge";
import { H3 } from "../../Text/Headings/H3";
import { Tabs } from "../../Tabs/Tabs";
import { SubPage } from "../../Helpers/SubPage";
import { useState } from "react";
import { Titleth, Contenttd, Generaltable } from "../../Helpers/Metatable";
import { SubscriptionMeta } from "@/lib/core/entity/rucio";
import { DateTag } from "../../Tags/DateTag";
import { BoolTag } from "../../Tags/BoolTag";
import { SubscriptionStateTag } from "../../Tags/SubscriptionStateTag";
import { HiChevronDown, HiChevronUp } from "react-icons/hi";
import { Collapsible } from "../../Helpers/Collapsible";
import { Accordion } from "../../Helpers/Accordion";
import { Code } from "../../Text/Content/Code";
import { AreaInput } from "../../Input/AreaInput";
import { Button } from "../../Button/Button";
import { PageSubscriptionJSONEditor } from "./PageSubscriptionJSONEditor";
import { Type } from "@sinclair/typebox";
import { SubscriptionFilter, SubscriptionReplicationRules } from "@/lib/core/entity/subscription";
import { Heading } from "../Helpers/Heading";
import { Body } from "../Helpers/Body";
import { SubscriptionViewModel } from "@/lib/infrastructure/data/view-model/subscriptions";

export interface PageSubscriptionPageProps {
subscriptionMeta: SubscriptionMeta
subscriptionViewModel: SubscriptionViewModel
editFilter: (filter: string) => void
editReplicationRules: (rules: string) => void
}
Expand All @@ -30,14 +24,14 @@ export const PageSubscription = (
props: PageSubscriptionPageProps
) => {
const [subpageIndex, setSubpageIndex] = useState<number>(0);
const meta = props.subscriptionMeta
const meta = props.subscriptionViewModel
return (
<div
className={twMerge("flex flex-col space-y-2 w-full")}
>
<Heading
title="View Subscription"
subtitle={`For subscription ${props.subscriptionMeta.name}`}
subtitle={`For subscription ${props.subscriptionViewModel.name}`}
/>
<Body>
<Tabs
Expand Down Expand Up @@ -86,10 +80,6 @@ export const PageSubscription = (
<Titleth>Account</Titleth>
<Contenttd>{meta.account}</Contenttd>
</tr>
<tr>
<Titleth>Comments</Titleth>
<Contenttd>{meta.comments}</Contenttd>
</tr>
<tr>
<Titleth>ID</Titleth>
<Contenttd>{meta.id}</Contenttd>
Expand Down
21 changes: 11 additions & 10 deletions src/component-library/components/Tags/SubscriptionStateTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const SubscriptionStateTag: (
React.FC<JSX.IntrinsicElements["span"] & { state: SubscriptionState; tiny?: boolean }>
) = (
{
state = SubscriptionState.Active,
state = SubscriptionState.ACTIVE,
tiny = false,
...props
}
Expand All @@ -29,19 +29,20 @@ export const SubscriptionStateTag: (
*/
const { className, ...otherprops } = props
const stateStrings: {[K in SubscriptionState]: string[]} = {
[SubscriptionState.Active]: ["Active", "A"],
[SubscriptionState.Inactive]: ["Inactive", "I"],
[SubscriptionState.New]: ["New", "N"],
[SubscriptionState.Updated]: ["Updated", "U"],
[SubscriptionState.Broken]: ["Broken", "B"]
[SubscriptionState.ACTIVE]: ["Active", "A"],
[SubscriptionState.INACTIVE]: ["Inactive", "I"],
[SubscriptionState.NEW]: ["New", "N"],
[SubscriptionState.UPDATED]: ["Updated", "U"],
[SubscriptionState.BROKEN]: ["Broken", "B"],
[SubscriptionState.UNKNOWN]: ["Unknown", "?"],
}
return (
<span
className={twMerge(
state === SubscriptionState.Active ? "bg-blue-300 border-blue-700 dark:bg-blue-700 dark:border-blue-200" : (
state === SubscriptionState.Inactive ? "bg-gray-300 border-gray-700 dark:bg-gray-700 dark:border-gray-200" : (
state === SubscriptionState.New ? "bg-teal-300 border-teal-700 dark:bg-teal-700 dark:border-teal-200" : (
state === SubscriptionState.Updated ? "bg-green-300 border-green-700 dark:bg-green-700 dark:border-green-200" :
state === SubscriptionState.ACTIVE ? "bg-blue-300 border-blue-700 dark:bg-blue-700 dark:border-blue-200" : (
state === SubscriptionState.INACTIVE ? "bg-gray-300 border-gray-700 dark:bg-gray-700 dark:border-gray-200" : (
state === SubscriptionState.NEW ? "bg-teal-300 border-teal-700 dark:bg-teal-700 dark:border-teal-200" : (
state === SubscriptionState.UPDATED ? "bg-green-300 border-green-700 dark:bg-green-700 dark:border-green-200" :
"bg-red-300 border-red-700 dark:bg-red-700 dark:border-red-200" // broken

)
Expand Down
11 changes: 9 additions & 2 deletions src/lib/common/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Headers as NodeFetchHeaders } from 'node-fetch'
import { RequestInit, Headers } from 'node-fetch'

import { Agent } from 'https'
/**
* @description Represents the headers of a HTTP request
*/
Expand All @@ -15,9 +15,10 @@ export type HTTPRequest = {
/**
* Prepares the request arguments for an HTTP request.
* @param {HTTPRequest} request - The HTTP request to prepare arguments for.
* @param {boolean} disableSSL - A boolean value that indicates whether SSL should be disabled.
* @returns {{ url: string | URL; requestArgs: RequestInit }} - An object containing the URL and request arguments.
*/
export function prepareRequestArgs(request: HTTPRequest): { url: string | URL; requestArgs: RequestInit } {
export function prepareRequestArgs(request: HTTPRequest, disableSSL: boolean = false): { url: string | URL; requestArgs: RequestInit } {
if (request.params) {
const url = new URL(request.url);
Object.keys(request.params).forEach((key) =>
Expand All @@ -34,5 +35,11 @@ export function prepareRequestArgs(request: HTTPRequest): { url: string | URL; r
if (request.headers) {
requestArgs.headers = request.headers as Headers;
}

if(disableSSL) {
requestArgs.agent = new Agent({
rejectUnauthorized: false
})
}
return { url: request.url, requestArgs };
}
8 changes: 8 additions & 0 deletions src/lib/core/dto/subscription-dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { BaseDTO } from "@/lib/sdk/dto";
import { Subscription } from "../entity/rucio";

/**
* Data Transfer Object for GET Subscription Endpoint
*/
export interface SubscriptionDTO extends BaseDTO, Subscription {
}
31 changes: 23 additions & 8 deletions src/lib/core/entity/rucio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,23 @@ export type Rule = {
locks_stuck_cnt: number;
}

export type SubscriptionMeta = {
/**
* Represents the replication rule metadata avilable for a rule in a subscription.
*/
export type SubscriptionReplicationRule = {
activity: string;
rse_expression: string;
source_replica_expression?: string;
copies: string;
lifetime: number;
comments?: string;
};

/**
* Represents a subscription in Rucio.
*/
export type Subscription = {
account: string
comments: string
created_at: DateISO
id: string
last_processed: DateISO
Expand All @@ -111,7 +125,7 @@ export type SubscriptionMeta = {
updated_at: DateISO
// more difficult datatypes, cast as string for now:
filter: string
replication_rules: string
replication_rules: SubscriptionReplicationRule[]
}

export type SubscriptionRuleStates = {
Expand Down Expand Up @@ -201,11 +215,12 @@ export enum RuleState {
}

export enum SubscriptionState {
Active = "A",
Inactive = "I",
New = "N",
Updated = "U",
Broken = "B",
ACTIVE = "A",
INACTIVE = "I",
NEW = "N",
UPDATED = "U",
BROKEN = "B",
UNKNOWN = "UNKNOWN",
}

// octal representation of the blockstate
Expand Down
12 changes: 12 additions & 0 deletions src/lib/core/port/primary/get-subscription-ports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BaseAuthenticatedInputPort, BaseOutputPort } from "@/lib/sdk/primary-ports";
import { GetSubscriptionError, GetSubscriptionRequest, GetSubscriptionResponse } from "../../usecase-models/get-subscription-usecase-models";

/**
* @interface GetSubscriptionInputPort representing the GetSubscription usecase.
*/
export interface GetSubscriptionInputPort extends BaseAuthenticatedInputPort<GetSubscriptionRequest> {}

/**
* @interface GetSubscriptionOutputPort representing the GetSubscription presenter.
*/
export interface GetSubscriptionOutputPort extends BaseOutputPort<GetSubscriptionResponse, GetSubscriptionError> {}
11 changes: 11 additions & 0 deletions src/lib/core/port/secondary/subscription-gateway-output-port.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SubscriptionDTO } from "../../dto/subscription-dto";

export default interface SubscriptionGatewayOutputPort {
/**
* Makes a GET request to the Rucio Server to retrieve a subscription
* @param rucioAuthToken A valid rucio auth token
* @param account The rucio account name
* @param name The rucio subscription name
*/
get(rucioAuthToken: string, account: string, name: string): Promise<SubscriptionDTO>
}
2 changes: 0 additions & 2 deletions src/lib/core/use-case/did-meta-usecase.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { BaseHttpErrorTypes } from "@/lib/sdk/http";
import { BaseUseCase } from "@/lib/sdk/usecase";
import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models";
import { de } from "@faker-js/faker";
import { injectable } from "inversify";
import { DIDMetaDTO } from "../dto/did-dto";
import { DIDMetaInputPort, type DIDMetaOutputPort } from "../port/primary/did-meta-ports";
Expand Down
75 changes: 75 additions & 0 deletions src/lib/core/use-case/get-subscription-usecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { BaseUseCase } from "@/lib/sdk/usecase";
import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models";
import { injectable } from "inversify";
import { SubscriptionDTO } from "../dto/subscription-dto";
import type { GetSubscriptionInputPort, GetSubscriptionOutputPort } from "../port/primary/get-subscription-ports";
import type SubscriptionGatewayOutputPort from "../port/secondary/subscription-gateway-output-port";
import { GetSubscriptionError, GetSubscriptionRequest, GetSubscriptionResponse } from "../usecase-models/get-subscription-usecase-models";


@injectable()
class GetSubscriptionUseCase extends BaseUseCase<AuthenticatedRequestModel<GetSubscriptionRequest>, GetSubscriptionResponse, GetSubscriptionError, SubscriptionDTO> implements GetSubscriptionInputPort {
constructor(
protected readonly presenter: GetSubscriptionOutputPort,
private readonly gateway: SubscriptionGatewayOutputPort,
){
super(presenter)
}
validateRequestModel(requestModel: AuthenticatedRequestModel<GetSubscriptionRequest>): GetSubscriptionError | undefined {
if(requestModel.account === '' || requestModel.account === undefined) {
return {
error: 'INVALID_REQUEST',
message: 'Account is required',
} as GetSubscriptionError
}

if(requestModel.sessionAccount !== requestModel.account) {
return {
error: 'INVALID_ACCOUNT',
message: 'Account specified in the request is not same as the account present in the session',
} as GetSubscriptionError
}

if(requestModel.name === '' || requestModel.name === undefined) {
return {
error: 'INVALID_REQUEST',
message: 'Name is required',
} as GetSubscriptionError
}
if(requestModel.rucioAuthToken === '' || requestModel.rucioAuthToken === undefined) {
return {
error: 'INVALID_AUTH',
message: 'Auth token is required',
} as GetSubscriptionError
}
return undefined;
}

async makeGatewayRequest(requestModel: AuthenticatedRequestModel<GetSubscriptionRequest>): Promise<SubscriptionDTO> {
const dto: SubscriptionDTO = await this.gateway.get(requestModel.rucioAuthToken, requestModel.account, requestModel.name);
return dto;
}

handleGatewayError(error: SubscriptionDTO): GetSubscriptionError {
return {
status: 'error',
error: error.message,
message: `Gateway responded with ${error.error?.errorCode}, ${error.error?.errorMessage}`
} as GetSubscriptionError
}

processDTO(dto: SubscriptionDTO): { data: GetSubscriptionResponse | GetSubscriptionError; status: "success" | "error"; } {
// copy all fields from dto to response model except success
const responseModel: GetSubscriptionResponse = {
...dto,
status: 'success',
}

return {
status: 'success',
data: responseModel,
}
}
}

export default GetSubscriptionUseCase;
19 changes: 19 additions & 0 deletions src/lib/core/usecase-models/get-subscription-usecase-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BaseErrorResponseModel, BaseResponseModel } from "@/lib/sdk/usecase-models";
import { Subscription } from "../entity/rucio";

export interface GetSubscriptionRequest {
account: string;
sessionAccount: string;
name: string;
}

export interface GetSubscriptionResponse extends Subscription, BaseResponseModel {}

/**
* Error Response Model for GET Subscription UseCase
* @property error - Error Message
* INVALID_ACCOUNT: The account specified in the request is not same as the account present in the session
*/
export interface GetSubscriptionError extends BaseErrorResponseModel {
error: 'SUBSCRIPTION_NOT_FOUND' | 'UNKNOWN_ERROR' | 'INVALID_REQUEST' | 'INVALID_AUTH' | 'INVALID_ACCOUNT';
}
1 change: 1 addition & 0 deletions src/lib/core/usecase-models/list-dids-usecase-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export interface ListDIDsResponse extends DID, BaseResponseModel {
}

export interface ListDIDsError extends BaseErrorResponseModel {
name: string;
error: 'Invalid DID Query' | 'Unknown Error' | 'Invalid Request' ;
}
6 changes: 3 additions & 3 deletions src/lib/infrastructure/auth/session-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export function withSessionRoute(handler: NextApiHandler) {

/**
* A wrapper around Next API routes that require a valid rucioAuthToken
* @param handler (req, res, validAuthToken) => Promise<void> or a NEXT.js API route that required a valid rucioAuthToken
* @param handler (req, res, validAuthToken, sessionUser?) => Promise<void> or a NEXT.js API route that required a valid rucioAuthToken
* @returns the wrapped route with a valid rucioAuthToken injected or returns a HTTP 401 error response
*/
export function withAuthenticatedSessionRoute(handler: (req: NextApiRequest, res: NextApiResponse, validAuthToken: string) => Promise<void>) {
export function withAuthenticatedSessionRoute(handler: (req: NextApiRequest, res: NextApiResponse, validAuthToken: string, sessionUser?: SessionUser) => Promise<void>) {
return withIronSessionApiRoute(async (req, res) => {
const session = req.session as IronSession
if(!session) {
Expand Down Expand Up @@ -98,7 +98,7 @@ export function withAuthenticatedSessionRoute(handler: (req: NextApiRequest, res
return
}

return handler(req, res, validToken)
return handler(req, res, validToken, sessionUser)
}, sessionOptions)
}

Expand Down
Loading

0 comments on commit cc51d88

Please sign in to comment.