From 3fa456b362d1d3bc924f660bbeaaf3c006affc48 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:06:24 +0000 Subject: [PATCH 01/20] change schema --- src/gql/schema.graphql | 3627 +++++++++++++++------------------------- 1 file changed, 1361 insertions(+), 2266 deletions(-) diff --git a/src/gql/schema.graphql b/src/gql/schema.graphql index e08d8c0e7..dc709d2c3 100644 --- a/src/gql/schema.graphql +++ b/src/gql/schema.graphql @@ -1,93 +1,60 @@ +schema { + query: Query + mutation: Mutation + subscription: Subscription +} +"The `BigInt` scalar type represents non-fractional signed whole numeric values." +scalar BigInt +"A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar." +scalar DateTime +scalar DeploymentMeta +scalar EventProperties +"The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)." +scalar JSON +""" +The ServiceVariables scalar type represents values as the TypeScript type: Record. Example: "{ foo: 'bar', baz: 'qux' }" +""" +scalar ServiceVariables +scalar SubscriptionPlanLimit +scalar TemplateConfig +scalar TemplateMetadata +scalar TemplateServiceConfig +scalar TemplateVolume +"The `Upload` scalar type represents a file upload." +scalar Upload type AccessRule { disallowed: String } - -enum ActiveFeatureFlag { - GITHUB_REPO_FROM_URL - NEW_DASHBOARD_UI -} - -input AdminDeploymentListInput { - filter: String - status: DeploymentStatus -} - -type AdminStats { - deploysFailedLastHour: Int! - deploysInProgressHour: Int! - deploysSuccessfulLastHour: Int! - latestDeploys: [Deployment!]! - latestTeams: [Team!]! - numSubscribed: Int! - numTeams: Int! - totalPlatformUsage: TotalUsage -} - -"""The aggregated usage of a single measurement.""" +"The aggregated usage of a single measurement." type AggregatedUsage { - """The measurement that was aggregated.""" + "The measurement that was aggregated." measurement: MetricMeasurement! - - """ - The tags that were used to group the metric. Only the tags that were used in the `groupBy` will be present. - """ + "The tags that were used to group the metric. Only the tags that were used in the `groupBy` will be present." tags: MetricTags! - - """The aggregated value.""" + "The aggregated value." value: Float! } - type AllDomains { customDomains: [CustomDomain!]! serviceDomains: [ServiceDomain!]! } - type ApiToken implements Node { displayToken: String! id: ID! name: String! teamId: String } - -input ApiTokenCreateInput { - name: String! - teamId: String -} - type BanReasonHistory implements Node { actor: User! banReason: String createdAt: DateTime! id: ID! } - -input BaseEnvironmentOverrideInput { - baseEnvironmentOverrideId: String -} - -""" -The `BigInt` scalar type represents non-fractional signed whole numeric values. -""" -scalar BigInt - -"""The billing period for a customers subscription.""" +"The billing period for a customers subscription." type BillingPeriod { end: DateTime! start: DateTime! } - -enum Builder { - HEROKU - NIXPACKS - PAKETO -} - -enum CDNProvider { - DETECTED_CDN_PROVIDER_CLOUDFLARE - DETECTED_CDN_PROVIDER_UNSPECIFIED - UNRECOGNIZED -} - type CertificatePublicData { domainNames: [String!]! expiresAt: DateTime @@ -95,25 +62,7 @@ type CertificatePublicData { issuedAt: DateTime keyType: KeyType! } - -enum CertificateStatus { - CERTIFICATE_STATUS_TYPE_ISSUE_FAILED - CERTIFICATE_STATUS_TYPE_ISSUING - CERTIFICATE_STATUS_TYPE_UNSPECIFIED - CERTIFICATE_STATUS_TYPE_VALID - UNRECOGNIZED -} - -input ChangelogSendInput { - changelogId: String! - changelogSlug: String! - changelogTitle: String! - isTestEmail: Boolean! -} - -""" -[Experimental] A changeset represents a single change to a service, plugin or variable. -""" +"[Experimental] A changeset represents a single change to a service, plugin or variable." type Changeset implements Node { createdAt: DateTime! id: ID! @@ -123,63 +72,25 @@ type Changeset implements Node { service: Service user: User } - -""" -[Experimental] An accumulated set of changes calculated from comparing two environments. -""" +"[Experimental] An accumulated set of changes calculated from comparing two environments." type ChangesetDiff { payload: JSON! plugin: Plugin service: Service } - type CnameCheck { link: String message: String! status: CnameCheckStatus! } - -enum CnameCheckStatus { - ERROR - INFO - INVALID - VALID - WAITING -} - -type CompositeScore { - accountScore: Float! - clashingSessionsScore: Float! - compositeScore: Float! - contributionsScore: Float! - flaggedRepos: [String!]! - networkScore: Float! - profileScore: Float! - repoScore: Float! - totalRepos: Int! -} - type Container implements Node { createdAt: DateTime! deletedAt: DateTime environmentId: String! id: ID! + migratedAt: DateTime pluginId: String! } - -type ContainerInfo { - host: String! - id: String! - image: String! - labels: [ContainerLabel!]! - status: String! -} - -type ContainerLabel { - key: String! - value: String! -} - type Credit implements Node { amount: Float! createdAt: DateTime! @@ -189,15 +100,6 @@ type Credit implements Node { type: CreditType! updatedAt: DateTime! } - -enum CreditType { - APPLIED - CREDIT - DEBIT - STRIPE - WAIVED -} - type CustomDomain implements Domain { cnameCheck: CnameCheck! @deprecated(reason: "Use the `status` field instead.") createdAt: DateTime @@ -209,26 +111,23 @@ type CustomDomain implements Domain { status: CustomDomainStatus! updatedAt: DateTime } - -input CustomDomainCreateInput { - domain: String! - environmentId: String! - serviceId: String! -} - type CustomDomainStatus { cdnProvider: CDNProvider certificateStatus: CertificateStatus! certificates: [CertificatePublicData!] dnsRecords: [DNSRecords!]! } - type Customer implements Node { appliedCredits: Float! billingEmail: String billingPeriod: BillingPeriod! creditBalance: Float! - credits(after: String, before: String, first: Int, last: Int): CustomerCreditsConnection! + credits( + after: String + before: String + first: Int + last: Int + ): CustomerCreditsConnection! defaultPaymentMethod: PaymentMethod defaultPaymentMethodId: String id: ID! @@ -236,6 +135,7 @@ type Customer implements Node { isPrepaying: Boolean! isTrialing: Boolean! isUsageSubscriber: Boolean! + planLimitOverride: PlanLimitOverride remainingUsageCreditBalance: Float! state: SubscriptionState! stripeCustomerId: String! @@ -244,35 +144,14 @@ type Customer implements Node { usageLimit: UsageLimit userId: String } - -input CustomerApplyCreditInput { - amountDollars: Int! - memo: String -} - -input CustomerAttachPaymentMethodInput { - paymentMethodId: String! - validateWithHold: Boolean -} - -input CustomerCreateBillingPortalInput { - redirectUrl: String! -} - -input CustomerCreateUsageSubscriptionInput { - paymentMethodId: String -} - type CustomerCreditsConnection { edges: [CustomerCreditsConnectionEdge!]! pageInfo: PageInfo! } - type CustomerCreditsConnectionEdge { cursor: String! node: Credit! } - type CustomerInvoice { amountPaid: Float! hostedURL: String @@ -286,17 +165,6 @@ type CustomerInvoice { subscriptionId: String total: Int! } - -input CustomerPurchaseCreditsInput { - amountDollars: Int! - paymentMethodId: String -} - -input CustomerRetryInvoiceInput { - invoiceId: String! - paymentMethodId: String! -} - type CustomerSubscription { billingCycleAnchor: DateTime! cancelAt: String @@ -310,33 +178,6 @@ type CustomerSubscription { nextInvoiceDate: String! status: String! } - -input CustomerUpdateBillingEmailInput { - email: String! -} - -enum DNSRecordPurpose { - DNS_RECORD_PURPOSE_ACME_DNS01_CHALLENGE - DNS_RECORD_PURPOSE_TRAFFIC_ROUTE - DNS_RECORD_PURPOSE_UNSPECIFIED - UNRECOGNIZED -} - -enum DNSRecordStatus { - DNS_RECORD_STATUS_PROPAGATED - DNS_RECORD_STATUS_REQUIRES_UPDATE - DNS_RECORD_STATUS_UNSPECIFIED - UNRECOGNIZED -} - -enum DNSRecordType { - DNS_RECORD_TYPE_A - DNS_RECORD_TYPE_CNAME - DNS_RECORD_TYPE_NS - DNS_RECORD_TYPE_UNSPECIFIED - UNRECOGNIZED -} - type DNSRecords { currentValue: String! fqdn: String! @@ -347,12 +188,6 @@ type DNSRecords { status: DNSRecordStatus! zone: String! } - -""" -A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. -""" -scalar DateTime - type Deployment implements Node { canRedeploy: Boolean! canRollback: Boolean! @@ -369,50 +204,14 @@ type Deployment implements Node { staticUrl: String status: DeploymentStatus! suggestAddServiceDomain: Boolean! + updatedAt: DateTime! url: String } - -type DeploymentByDomain { - activeDeployment: Deployment - latestDeployment: Deployment - projectId: String -} - -input DeploymentListInput { - environmentId: String - includeDeleted: Boolean - projectId: String - serviceId: String - status: DeploymentStatusInput -} - -scalar DeploymentMeta - type DeploymentSnapshot implements Node { createdAt: DateTime! id: ID! updatedAt: DateTime! } - -enum DeploymentStatus { - BUILDING - CRASHED - DEPLOYING - FAILED - INITIALIZING - QUEUED - REMOVED - REMOVING - SKIPPED - SUCCESS - WAITING -} - -input DeploymentStatusInput { - in: [DeploymentStatus!] - notIn: [DeploymentStatus!] -} - type DeploymentTrigger implements Node { baseEnvironmentOverrideId: String branch: String! @@ -425,44 +224,10 @@ type DeploymentTrigger implements Node { serviceId: String validCheckSuites: Int! } - -input DeploymentTriggerCreateInput { - branch: String! - checkSuites: Boolean - environmentId: String! - projectId: String! - provider: String! - repository: String! - rootDirectory: String - serviceId: String! -} - -input DeploymentTriggerUpdateInput { - branch: String - checkSuites: Boolean - repository: String - rootDirectory: String -} - -type DiscordServerInfo { - presenceCount: String -} - -interface Domain { - createdAt: DateTime - deletedAt: DateTime - domain: String! - environmentId: String! - id: ID! - serviceId: String! - updatedAt: DateTime -} - type DomainAvailable { available: Boolean! message: String! } - type DomainWithStatus { cdnProvider: CDNProvider certificateStatus: CertificateStatus! @@ -470,59 +235,60 @@ type DomainWithStatus { dnsRecords: [DNSRecords!]! domain: Domain } - type Environment implements Node { - """ - [Experimental] Returns the diff between this environment and its parent one. - """ + "[Experimental] Returns the diff between this environment and its parent one." changes: [ChangesetDiff!]! createdAt: DateTime! deletedAt: DateTime - deploymentTriggers(after: String, before: String, first: Int, last: Int): EnvironmentDeploymentTriggersConnection! - deployments(after: String, before: String, first: Int, last: Int): EnvironmentDeploymentsConnection! + deploymentTriggers( + after: String + before: String + first: Int + last: Int + ): EnvironmentDeploymentTriggersConnection! + deployments( + after: String + before: String + first: Int + last: Int + ): EnvironmentDeploymentsConnection! id: ID! isEphemeral: Boolean! meta: EnvironmentMeta name: String! projectId: String! - serviceInstances(after: String, before: String, first: Int, last: Int): EnvironmentServiceInstancesConnection! + serviceInstances( + after: String + before: String + first: Int + last: Int + ): EnvironmentServiceInstancesConnection! sourceEnvironment: Environment unmergedChangesCount: Int updatedAt: DateTime! - variables(after: String, before: String, first: Int, last: Int): EnvironmentVariablesConnection! -} - -input EnvironmentCreateInput { - ephemeral: Boolean - name: String! - projectId: String! - - """ - [Experimental] Specifying this field will create a new environment that is a fork of the specified environment. Changes made to forked environments will not affect other environments, and vice versa. - """ - sourceEnvironmentId: String + variables( + after: String + before: String + first: Int + last: Int + ): EnvironmentVariablesConnection! } - type EnvironmentDeploymentTriggersConnection { edges: [EnvironmentDeploymentTriggersConnectionEdge!]! pageInfo: PageInfo! } - type EnvironmentDeploymentTriggersConnectionEdge { cursor: String! node: DeploymentTrigger! } - type EnvironmentDeploymentsConnection { edges: [EnvironmentDeploymentsConnectionEdge!]! pageInfo: PageInfo! } - type EnvironmentDeploymentsConnectionEdge { cursor: String! node: Deployment! } - type EnvironmentMeta { baseBranch: String branch: String @@ -530,43 +296,30 @@ type EnvironmentMeta { prRepo: String prTitle: String } - type EnvironmentServiceInstancesConnection { edges: [EnvironmentServiceInstancesConnectionEdge!]! pageInfo: PageInfo! } - type EnvironmentServiceInstancesConnectionEdge { cursor: String! node: ServiceInstance! } - -input EnvironmentTriggersDeployInput { - environmentId: String! - projectId: String! - serviceId: String! -} - type EnvironmentVariablesConnection { edges: [EnvironmentVariablesConnectionEdge!]! pageInfo: PageInfo! } - type EnvironmentVariablesConnectionEdge { cursor: String! node: Variable! } - -"""The estimated usage of a single measurement.""" +"The estimated usage of a single measurement." type EstimatedUsage { - """The estimated value.""" + "The estimated value." estimatedValue: Float! - - """The measurement that was estimated.""" + "The measurement that was estimated." measurement: MetricMeasurement! projectId: String! } - type Event implements Node { action: String! createdAt: DateTime! @@ -578,44 +331,9 @@ type Event implements Node { project: Project! projectId: String! } - -input EventBatchTrackInput { - events: [EventTrackInput!]! -} - -scalar EventProperties - -input EventTrackInput { - eventName: String! - properties: EventProperties - ts: String! -} - -input ExplicitOwnerInput { - """The ID of the owner""" - id: String! - - """The type of owner""" - type: ResourceOwnerType! -} - -input FeatureFlagAddInput { - flag: ActiveFeatureFlag! -} - -input FeatureFlagRemoveInput { - flag: ActiveFeatureFlag! -} - type GitHubBranch { name: String! } - -type GitHubEvent { - createdAt: DateTime - type: String! -} - type GitHubRepo { defaultBranch: String! fullName: String! @@ -624,95 +342,41 @@ type GitHubRepo { isPrivate: Boolean! name: String! } - -input GitHubRepoDeployInput { - projectId: String! - repo: String! -} - -input GitHubRepoUpdateInput { - environmentId: String! - projectId: String! - serviceId: String! -} - -input GroupedUsageAnomaliesInput { - filters: GroupedUsageAnomaliesInputFilters! -} - -input GroupedUsageAnomaliesInputFilters { - needsAction: Boolean! -} - -type GroupedUsageAnomaly { - anomalies: [UsageAnomaly!]! - service: Service! -} - type HerokuApp { id: String! name: String! } - -input HerokuImportVariablesInput { - environmentId: String! - herokuAppId: String! - projectId: String! - serviceId: String! -} - type Incident { id: String! message: String! status: IncidentStatus! url: String! } - -enum IncidentStatus { - IDENTIFIED - INVESTIGATING - MONITORING - RESOLVED -} - type Integration implements Node { config: JSON! id: ID! name: String! projectId: String! } - type IntegrationAuth implements Node { id: ID! - integrations(after: String, before: String, first: Int, last: Int): IntegrationAuthIntegrationsConnection! + integrations( + after: String + before: String + first: Int + last: Int + ): IntegrationAuthIntegrationsConnection! provider: String! providerId: String! } - type IntegrationAuthIntegrationsConnection { edges: [IntegrationAuthIntegrationsConnectionEdge!]! pageInfo: PageInfo! } - type IntegrationAuthIntegrationsConnectionEdge { cursor: String! node: Integration! } - -input IntegrationCreateInput { - config: JSON! - integrationAuthId: String - name: String! - projectId: String! -} - -input IntegrationUpdateInput { - config: JSON! - integrationAuthId: String - name: String! - projectId: String! -} - type InviteCode implements Node { code: String! createdAt: DateTime! @@ -721,61 +385,25 @@ type InviteCode implements Node { projectId: String! role: ProjectRole! } - -""" -The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). -""" -scalar JSON - -input JobApplicationCreateInput { - email: String! - jobId: String! - name: String! - resume: Upload! - why: String! -} - -enum KeyType { - KEY_TYPE_ECDSA - KEY_TYPE_RSA_2048 - KEY_TYPE_RSA_4096 - KEY_TYPE_UNSPECIFIED - UNRECOGNIZED -} - -type LockdownStatus { - allProvisionsDisabledMsg: String - anonProvisionsDisabledMsg: String - freeProvisionsDisabledMsg: String - nonProProvisionsDisabledMsg: String - signupsDisabledMsg: String -} - -"""The result of a logs query.""" +"The result of a logs query." type Log { - """The attributes that were parsed from a structured log""" + "The attributes that were parsed from a structured log" attributes: [LogAttribute!]! - - """The contents of the log message""" + "The contents of the log message" message: String! - - """The severity of the log message (eg. err)""" + "The severity of the log message (eg. err)" severity: String - - """The tags that were associated with the log""" + "The tags that were associated with the log" tags: LogTags - - """The timestamp of the log message in format RFC3339 (nano)""" + "The timestamp of the log message in format RFC3339 (nano)" timestamp: String! } - -"""The attributes associated with a structured log""" +"The attributes associated with a structured log" type LogAttribute { key: String! value: String! } - -"""The tags associated with a specific log""" +"The tags associated with a specific log" type LogTags { deploymentId: String deploymentInstanceId: String @@ -785,70 +413,20 @@ type LogTags { serviceId: String snapshotId: String } - -input LoginSessionAuthInput { - code: String! - hostname: String -} - type Maintenance { id: String! message: String! status: MaintenanceStatus! url: String! } - -enum MaintenanceStatus { - COMPLETED - INPROGRESS - NOTSTARTEDYET -} - -input MergeChange { - action: String! - name: String! - serviceId: String - type: String! - value: String! -} - -"""A single sample of a metric.""" +"A single sample of a metric." type Metric { - """ - The timestamp of the sample. Represented has number of seconds since the Unix epoch. - """ + "The timestamp of the sample. Represented has number of seconds since the Unix epoch." ts: Int! - - """The value of the sample.""" + "The value of the sample." value: Float! } - -"""A thing that can be measured on Railway.""" -enum MetricMeasurement { - CPU_USAGE - DISK_USAGE_GB - EPHEMERAL_DISK_USAGE_GB - MEASUREMENT_UNSPECIFIED - MEMORY_USAGE_GB - NETWORK_RX_GB - NETWORK_TX_GB - UNRECOGNIZED -} - -"""A property that can be used to group metrics.""" -enum MetricTag { - DEPLOYMENT_ID - DEPLOYMENT_INSTANCE_ID - ENVIRONMENT_ID - KEY_UNSPECIFIED - PLUGIN_ID - PROJECT_ID - SERVICE_ID - UNRECOGNIZED - VOLUME_ID -} - -"""The tags that were used to group the metric.""" +"The tags that were used to group the metric." type MetricTags { deploymentId: String environmentId: String @@ -857,750 +435,398 @@ type MetricTags { serviceId: String volumeId: String } - -"""The result of a metrics query.""" +"The result of a metrics query." type MetricsResult { - """The measurement of the metric.""" + "The measurement of the metric." measurement: MetricMeasurement! - - """ - The tags that were used to group the metric. Only the tags that were used to by will be present. - """ + "The tags that were used to group the metric. Only the tags that were used to by will be present." tags: MetricTags! - - """The samples of the metric.""" + "The samples of the metric." values: [Metric!]! } - -input MissingCommandAlertInput { - page: String! - text: String! -} - -"""A collection belonging to a MongoDB database""" -type MongoCollection { - data: JSON! - name: String! -} - type Mutation { - """Delete the user with the provided userId""" - adminDelete(userId: String!): Boolean! - - """Creates a new API token.""" + "Creates a new API token." apiTokenCreate(input: ApiTokenCreateInput!): String! - - """Deletes an API token.""" + "Deletes an API token." apiTokenDelete(id: String!): Boolean! - - """Sets the base environment override for a deployment trigger.""" - baseEnvironmentOverride(id: String!, input: BaseEnvironmentOverrideInput!): Boolean! - - """Triggers an email with the changelog for the provided slug""" - changelogSend(input: ChangelogSendInput!): Boolean! - - """Creates a new refund support request""" - createRefundRequest(input: RefundFormInput!): Boolean! - - """Creates a new Talk To An Engineer support request""" - createTalkToAnEngineerRequest(input: TalkToAnEngineerFormInput!): Boolean! - - """Creates a new custom domain.""" + "Sets the base environment override for a deployment trigger." + baseEnvironmentOverride( + id: String! + input: BaseEnvironmentOverrideInput! + ): Boolean! + "Creates a new custom domain." customDomainCreate(input: CustomDomainCreateInput!): CustomDomain! - - """Deletes a custom domain.""" + "Deletes a custom domain." customDomainDelete(id: String!): Boolean! - - """Issues a new certificate""" - customDomainIssueCertificate(id: String!): Boolean! - - """Apply a credit to a customer""" - customerApplyCredit(id: String!, input: CustomerApplyCreditInput!): Boolean! - - """Attach a payment method to a customer""" - customerAttachPaymentMethod(id: String!, input: CustomerAttachPaymentMethodInput!): JSON! - - """Create a Stripe billing portal for a customer""" - customerCreateBillingPortal(id: String!, input: CustomerCreateBillingPortalInput!): String! - - """Create a usage based subscription for a customer""" - customerCreateUsageSubscription(id: String!, input: CustomerCreateUsageSubscriptionInput!): JSON! - - """Migrate a customer to the hobby plan""" + "Migrate a customer to the hobby plan" customerMigrateToHobbyPlan(id: String!): Boolean! - - """Purchase credits for a Railway customer""" - customerPurchaseCredits(id: String!, input: CustomerPurchaseCreditsInput!): JSON! - - """Report all usage for a customer and the current billing period""" - customerReportUsage(customerId: String!): JSON! - - """Retry an invoice for a customer with a new payment method""" - customerRetryInvoice(id: String!, input: CustomerRetryInvoiceInput!): JSON! - - """Update a customer's billing email""" - customerUpdateBillingEmail(id: String!, input: CustomerUpdateBillingEmailInput!): Customer! - - """Cancels a deployment.""" + "Cancels a deployment." deploymentCancel(id: String!): Boolean! - - """Redeploys a deployment.""" + "Redeploys a deployment." deploymentRedeploy(id: String!): Deployment! - - """Removes a deployment.""" + "Removes a deployment." deploymentRemove(id: String!): Boolean! - - """Restarts a deployment.""" + "Restarts a deployment." deploymentRestart(id: String!): Boolean! - - """Rolls back to a deployment.""" + "Rolls back to a deployment." deploymentRollback(id: String!): Boolean! - - """Creates a deployment trigger.""" - deploymentTriggerCreate(input: DeploymentTriggerCreateInput!): DeploymentTrigger! - - """Deletes a deployment trigger.""" + "Creates a deployment trigger." + deploymentTriggerCreate( + input: DeploymentTriggerCreateInput! + ): DeploymentTrigger! + "Deletes a deployment trigger." deploymentTriggerDelete(id: String!): Boolean! - - """Updates a deployment trigger.""" - deploymentTriggerUpdate(id: String!, input: DeploymentTriggerUpdateInput!): DeploymentTrigger! - - """ - Change the User's account email if there is a valid change email request. - """ + "Updates a deployment trigger." + deploymentTriggerUpdate( + id: String! + input: DeploymentTriggerUpdateInput! + ): DeploymentTrigger! + "Change the User's account email if there is a valid change email request." emailChangeConfirm(nonce: String!): Boolean! - - """Initiate an email change request for a user""" + "Initiate an email change request for a user" emailChangeInitiate(newEmail: String!): Boolean! - - """Creates a new environment.""" + "Creates a new environment." environmentCreate(input: EnvironmentCreateInput!): Environment! - - """Deletes an environment.""" + "Deletes an environment." environmentDelete(id: String!): Boolean! - - """[Experimental] Merges the current environment with the parent one.""" + "[Experimental] Merges the current environment with the parent one." environmentMerge(changes: [MergeChange!], id: String!): Boolean! - - """Deploys all connected triggers for an environment.""" + "Deploys all connected triggers for an environment." environmentTriggersDeploy(input: EnvironmentTriggersDeployInput!): Boolean! - - """Track a batch of events for authenticated user""" + "Track a batch of events for authenticated user" eventBatchTrack(input: EventBatchTrackInput!): Boolean! - - """Track event for authenticated user""" + "Track event for authenticated user" eventTrack(input: EventTrackInput!): Boolean! - - """Agree to the fair use policy for the currently authenticated user""" + "Agree to the fair use policy for the currently authenticated user" fairUseAgree(agree: Boolean!): Boolean! - - """Add a feature flag for a user""" - featureFlagAdd(input: FeatureFlagAddInput!): Boolean! - - """Remove a feature flag for a user""" - featureFlagRemove(input: FeatureFlagRemoveInput!): Boolean! - - """Deploys a GitHub repo""" + "Add a feature flag for a user" + featureFlagAdd(input: FeatureFlagToggleInput!): Boolean! + "Remove a feature flag for a user" + featureFlagRemove(input: FeatureFlagToggleInput!): Boolean! + "Deploys a GitHub repo" githubRepoDeploy(input: GitHubRepoDeployInput!): Boolean! - - """Updates a GitHub repo through the linked template""" + "Updates a GitHub repo through the linked template" githubRepoUpdate(input: GitHubRepoUpdateInput!): Boolean! - - """ - Import variables from a Heroku app into a Railway service. Returns the number of variables imports - """ + "Import variables from a Heroku app into a Railway service. Returns the number of variables imports" herokuImportVariables(input: HerokuImportVariablesInput!): Int! - - """Create an integration for a project""" + "Create an integration for a project" integrationCreate(input: IntegrationCreateInput!): Integration! - - """Delete an integration for a project""" + "Delete an integration for a project" integrationDelete(id: String!): Boolean! - - """Update an integration for a project""" + "Update an integration for a project" integrationUpdate(id: String!, input: IntegrationUpdateInput!): Integration! - - """Join a project using an invite code""" + "Join a project using an invite code" inviteCodeUse(code: String!): Project! - - """Creates a new job application.""" + "Creates a new job application." jobApplicationCreate(input: JobApplicationCreateInput!): Boolean! - - """Auth a login session for a user""" + "Auth a login session for a user" loginSessionAuth(input: LoginSessionAuthInput!): Boolean! - - """Cancel a login session""" + "Cancel a login session" loginSessionCancel(code: String!): Boolean! - - """Get a token for a login session if it exists""" + "Get a token for a login session if it exists" loginSessionConsume(code: String!): String - - """Start a CLI login session""" + "Start a CLI login session" loginSessionCreate: String! - - """Verify if a login session is valid""" + "Verify if a login session is valid" loginSessionVerify(code: String!): Boolean! - - """Deletes session for current user if it exists""" + "Deletes session for current user if it exists" logout: Boolean! - - """Alert the team of a missing command palette command""" + "Alert the team of a missing command palette command" missingCommandAlert(input: MissingCommandAlertInput!): Boolean! - - """Delete an entire collection from a MongoDB container""" - mongoDeleteCollection(database: String!, environmentId: String!, name: String!, pluginId: String, serviceId: String): Boolean! - - """Delete an entire collection from a MongoDB container""" - mongoDeleteDocument(database: String!, environmentId: String!, id: String!, name: String!, pluginId: String, serviceId: String): Boolean! - - """Generate dummy data for a MongoDB container""" - mongoDummyData(database: String!, environmentId: String!, pluginId: String, serviceId: String): Boolean! - - """Insert a document into a MongoDB container""" - mongoInsertDocument(data: JSON!, database: String!, environmentId: String!, name: String!, pluginId: String, serviceId: String): String! - - """Update a document in a MongoDB container""" - mongoUpdateDocument(data: JSON!, database: String!, environmentId: String!, id: String!, name: String!, pluginId: String, serviceId: String): Boolean! - - """Toggles the specified platform service on or off.""" - platformServiceToggle(input: TogglePlatformServiceInput!): Boolean! - - """Creates a new plugin.""" + "Creates a new plugin." pluginCreate(input: PluginCreateInput!): Plugin! - - """Deletes a plugin.""" + @deprecated( + reason: "Plugins are deprecated on Railway. Use database templates instead." + ) + "Deletes a plugin." pluginDelete(environmentId: String, id: String!): Boolean! - - """Reset envs and container for a plugin in an environment""" + "Migrates a plugin to a V2 database" + pluginMigrate(id: String!, resetMigration: Boolean): String! + "Reset envs and container for a plugin in an environment" pluginReset(id: String!, input: ResetPluginInput!): Boolean! - - """Resets the credentials for a plugin in an environment""" - pluginResetCredentials(id: String!, input: ResetPluginCredentialsInput!): String! - - """Restarts a plugin.""" + "Resets the credentials for a plugin in an environment" + pluginResetCredentials( + id: String! + input: ResetPluginCredentialsInput! + ): String! + "Restarts a plugin." pluginRestart(id: String!, input: PluginRestartInput!): Plugin! - - """Force start a plugin""" + "Force start a plugin" pluginStart(id: String!, input: PluginRestartInput!): Boolean! - - """Updates an existing plugin.""" + "Updates an existing plugin." pluginUpdate(id: String!, input: PluginUpdateInput!): Plugin! - - """Update the email preferences for a user""" + "Update the email preferences for a user" preferencesUpdate(input: PreferencesUpdateData!): Preferences! - - """Create or get a private network.""" - privateNetworkCreateOrGet(input: PrivateNetworkCreateOrGetInput!): PrivateNetwork! - - """Create or get a private network endpoint.""" - privateNetworkEndpointCreateOrGet(input: PrivateNetworkEndpointCreateOrGetInput!): PrivateNetworkEndpoint! - - """Delete a private network endpoint.""" + "Create or get a private network." + privateNetworkCreateOrGet( + input: PrivateNetworkCreateOrGetInput! + ): PrivateNetwork! + "Create or get a private network endpoint." + privateNetworkEndpointCreateOrGet( + input: PrivateNetworkEndpointCreateOrGetInput! + ): PrivateNetworkEndpoint! + "Delete a private network endpoint." privateNetworkEndpointDelete(id: String!): Boolean! - - """Rename a private network endpoint.""" - privateNetworkEndpointRename(dnsName: String!, id: String!, privateNetworkId: String!): Boolean! - - """Delete all private networks for an environment.""" + "Rename a private network endpoint." + privateNetworkEndpointRename( + dnsName: String! + id: String! + privateNetworkId: String! + ): Boolean! + "Delete all private networks for an environment." privateNetworksForEnvironmentDelete(environmentId: String!): Boolean! - - """Claims a project.""" + "Claims a project." projectClaim(id: String!): Project! - - """Creates a new project.""" + "Creates a new project." projectCreate(input: ProjectCreateInput!): Project! - - """Deletes a project.""" + "Deletes a project." projectDelete(id: String!): Boolean! - - """Invite a user by email to a project""" + "Invite a user by email to a project" projectInviteUser(id: String!, input: ProjectInviteUserInput!): Boolean! - - """Leave project as currently authenticated user""" + "Leave project as currently authenticated user" projectLeave(id: String!): Boolean! - - """Remove user from a project""" + "Remove user from a project" projectMemberRemove(input: ProjectMemberRemoveInput!): [ProjectMember!]! - - """Change the role for a user within a project""" + "Change the role for a user within a project" projectMemberUpdate(input: ProjectMemberUpdateInput!): ProjectMember! - - """Create a token for a project that has access to a specific environment""" + "Create a token for a project that has access to a specific environment" projectTokenCreate(input: ProjectTokenCreateInput!): String! - - """Delete a project token""" + "Delete a project token" projectTokenDelete(id: String!): Boolean! - - """Confirm the transfer of project ownership""" + "Confirm the transfer of project ownership" projectTransferConfirm(input: ProjectTransferConfirmInput!): Boolean! - - """Initiate the transfer of project ownership""" + "Initiate the transfer of project ownership" projectTransferInitiate(input: ProjectTransferInitiateInput!): Boolean! - - """Transfer a project to a team""" - projectTransferToTeam(id: String!, input: ProjectTransferToTeamInput!): Boolean! - - """Transfer a project to a user""" + "Transfer a project to a team" + projectTransferToTeam( + id: String! + input: ProjectTransferToTeamInput! + ): Boolean! + "Transfer a project to a user" projectTransferToUser(id: String!): Boolean! - - """Updates a project.""" + "Updates a project." projectUpdate(id: String!, input: ProjectUpdateInput!): Project! - - """Deletes a ProviderAuth.""" + "Deletes a ProviderAuth." providerAuthRemove(id: String!): Boolean! - - """Generates a new set of recovery codes for the authenticated user.""" + "Generates a new set of recovery codes for the authenticated user." recoveryCodeGenerate: RecoveryCodes! - - """Validates a recovery code.""" + "Validates a recovery code." recoveryCodeValidate(input: RecoveryCodeValidateInput!): Boolean! - - """Delete a key in a Redis container""" - redisDeleteKey(environmentId: String!, key: String!, pluginId: String, serviceId: String): Boolean! - - """Generate dummy data for a Redis container""" - redisDummyData(environmentId: String!, pluginId: String, serviceId: String): Boolean! - - """Delete values to a hash in a Redis container""" - redisHashDelete(environmentId: String!, key: String!, pluginId: String, serviceId: String, values: [String!]!): Boolean! - - """Add values to a hash in a Redis container""" - redisHashSet(environmentId: String!, key: String!, pluginId: String, serviceId: String, values: JSON!): Boolean! - - """Pop a value from a list in a Redis container""" - redisPopList(environmentId: String!, key: String!, pluginId: String, serviceId: String, side: String!): Boolean! - - """Push a value to a list in a Redis container""" - redisPushList(environmentId: String!, key: String!, pluginId: String, serviceId: String, side: String!, values: [String!]!): Boolean! - - """Add a value from a set in a Redis container""" - redisSetAdd(environmentId: String!, key: String!, pluginId: String, serviceId: String, values: [String!]!): Boolean! - - """Set a keys expire time in seconds in a Redis container""" - redisSetExpire(environmentId: String!, key: String!, pluginId: String, serviceId: String, ttl: BigInt!): Boolean! - - """Set list index to a value Redis container""" - redisSetListIndex(environmentId: String!, index: Int!, key: String!, pluginId: String, serviceId: String, value: String!): Boolean! - - """Remove a value from a set in a Redis container""" - redisSetRemove(environmentId: String!, key: String!, pluginId: String, serviceId: String, values: [String!]!): Boolean! - - """Set a string value in a Redis container""" - redisStringSet(environmentId: String!, key: String!, pluginId: String, serviceId: String, value: String!): Boolean! - - """Updates the ReferralInfo for the authenticated user.""" + "Updates the ReferralInfo for the authenticated user." referralInfoUpdate(input: ReferralInfoUpdateInput!): ReferralInfo! - - """Remove a cached GitHub access token.""" - removeCachedGitHubAccessToken(userId: String!): Boolean! - - """Connect a service to a source""" + "Send a community thread notification email" + sendCommunityThreadNotificationEmail( + input: SendCommunityThreadNotificationEmailInput! + ): Boolean! + "Connect a service to a source" serviceConnect(id: String!, input: ServiceConnectInput!): Service! - - """Creates a new service.""" + "Creates a new service." serviceCreate(input: ServiceCreateInput!): Service! - - """Deletes a service.""" + "Deletes a service." serviceDelete( - """ - [Experimental] Environment ID. If the environment is a forked environment, the service will only be deleted in the specified environment, otherwise it will deleted in all environments that are not forks of other environments - """ + "[Experimental] Environment ID. If the environment is a forked environment, the service will only be deleted in the specified environment, otherwise it will deleted in all environments that are not forks of other environments" environmentId: String id: String! ): Boolean! - - """Disconnect a service from a repo""" + "Disconnect a service from a repo" serviceDisconnect(id: String!): Service! - - """Creates a new service domain.""" + "Creates a new service domain." serviceDomainCreate(input: ServiceDomainCreateInput!): ServiceDomain! - - """Deletes a service domain.""" + "Deletes a service domain." serviceDomainDelete(id: String!): Boolean! - - """Updates a service domain.""" + "Updates a service domain." serviceDomainUpdate(input: ServiceDomainUpdateInput!): Boolean! - - """Redeploy a service instance""" + "Redeploy a service instance" serviceInstanceRedeploy(environmentId: String!, serviceId: String!): Boolean! - - """Get a list of suggested variables for a services repo""" - serviceInstanceSuggestedVariables(environmentId: String!, serviceId: String!): JSON! - - """Update a service instance""" + "Update a service instance" serviceInstanceUpdate( - """ - [Experimental] Environment ID. If the environment is a fork, the service will only be updated in it. Otherwise it will updated in all environments that are not forks of other environments - """ + "[Experimental] Environment ID. If the environment is a fork, the service will only be updated in it. Otherwise it will updated in all environments that are not forks of other environments" environmentId: String input: ServiceInstanceUpdateInput! serviceId: String! ): Boolean! - - """Remove the upstream URL from all service instances for this service""" + "Remove the upstream URL from all service instances for this service" serviceRemoveUpstreamUrl(id: String!): Service! - - """Updates a service.""" + "Updates a service." serviceUpdate(id: String!, input: ServiceUpdateInput!): Service! - - """Deletes a session.""" + "Deletes a session." sessionDelete(id: String!): Boolean! - - """Configure a shared variable.""" + "Configure a shared variable." sharedVariableConfigure(input: SharedVariableConfigureInput!): Variable! - - """Insert a column in a table in a SQL database container""" - sqlColumnInsert(column: SQLColumnInput!, databaseType: String!, environmentId: String!, pluginId: String, serviceId: String, tableName: String!): Boolean! - - """Generate dummy data for a SQL database container""" - sqlDummyData(databaseType: String!, environmentId: String!, pluginId: String, serviceId: String): Boolean! - - """Run the raw SQL query provided by the user""" - sqlRawQueryRun(databaseType: String!, environmentId: String!, pluginId: String, query: String!, serviceId: String): SQLRawQueryResponse! - - """Insert a row into a SQL database container""" - sqlRowInsert(columns: [SQLRowInput!]!, databaseType: String!, environmentId: String!, pluginId: String, serviceId: String, tableName: String!): Boolean! - - """Update row in a table in a SQL database container""" - sqlRowUpdate(data: JSON!, databaseType: String!, environmentId: String!, pKey: String!, pKeyValue: String!, pluginId: String, serviceId: String, tableName: String!): Boolean! - - """Delete rows from a table in a SQL database container""" - sqlRowsDelete(columnName: String!, databaseType: String!, environmentId: String!, pluginId: String, rows: [String!]!, serviceId: String, tableName: String!): Boolean! - - """Create a table in a SQL database container""" - sqlTableCreate(columns: [SQLColumnInput!]!, databaseType: String!, environmentId: String!, name: String!, pluginId: String, serviceId: String): Boolean! - - """Delete a table in a SQL database container""" - sqlTableDelete(databaseType: String!, environmentId: String!, name: String!, pluginId: String, serviceId: String): Boolean! - - """Creates a new TCP proxy for a service instance.""" + "Creates a new TCP proxy for a service instance." tcpProxyCreate(input: TCPProxyCreateInput!): TCPProxy! - - """Deletes a TCP proxy by id""" + "Deletes a TCP proxy by id" tcpProxyDelete(id: String!): Boolean! - - """Ban a team.""" - teamBan(id: String!, input: TeamBanInput!): Boolean! - - """Bulk transfer projects from user to team""" + "Bulk transfer projects from user to team" teamBulkProjectTransfer(input: TeamBulkProjectTransferInput!): Boolean! - - """Create a team""" + "Create a team" teamCreate(input: TeamCreateInput!): Team! - - """Create a team and subscribe to the Pro plan""" - teamCreateAndSubscribe(input: TeamCreateAndSubscribeInput!): TeamCreateAndSubscribeResponse! - - """Delete a team and all data associated with it""" + "Create a team and subscribe to the Pro plan" + teamCreateAndSubscribe( + input: TeamCreateAndSubscribeInput! + ): TeamCreateAndSubscribeResponse! + "Delete a team and all data associated with it" teamDelete(id: String!): Boolean! - - """Get an invite code for a team and role""" + "Get an invite code for a team and role" teamInviteCodeCreate(id: String!, input: TeamInviteCodeCreateInput!): String! - - """Use an invite code to join a team""" + "Use an invite code to join a team" teamInviteCodeUse(code: String!): Team! - - """Leave a team""" + "Leave a team" teamLeave(id: String!): Boolean! - - """Changes a user team permissions.""" + "Changes a user team permissions." teamPermissionChange(input: TeamPermissionChangeInput!): Boolean! - - """Stop all deployments and plugins for a team.""" - teamResourcesStop(id: String!, input: TeamResourcesStopInput): Boolean! - - """Set Direct Support eligiblity for a team""" - teamSetIsEligibleForDirectSupport(isEligibleForDirectSupport: Boolean!, teamId: String!): Boolean! - - """Unban a team.""" - teamUnban(id: String!): Boolean! - - """Update a team by id""" + "Update a team by id" teamUpdate(id: String!, input: TeamUpdateInput!): Team! - - """Invite a user by email to a team""" + "Invite a user by email to a team" teamUserInvite(id: String!, input: TeamUserInviteInput!): Boolean! - - """Remove a user from a team""" + "Remove a user from a team" teamUserRemove(id: String!, input: TeamUserRemoveInput!): Boolean! - - """Logs panics from CLI to Datadog""" + "Logs panics from CLI to Datadog" telemetrySend(input: TelemetrySendInput!): Boolean! - - """Duplicates an existing template""" - templateClone(code: String!): Template! - - """Creates a template.""" + "Duplicates an existing template" + templateClone(input: TemplateCloneInput!): Template! + "Creates a template." templateCreate(input: TemplateCreateInput!): Template! - - """Deletes a template.""" + "Deletes a template." templateDelete(id: String!): Boolean! - - """Deploys a template.""" + "Deploys a template." templateDeploy(input: TemplateDeployInput!): TemplateDeployPayload! - - """Generate a template for a project""" + "Generate a template for a project" templateGenerate(input: TemplateGenerateInput!): Template! - - """Hides a template.""" - templateHide(id: String!): Boolean! - - """Backfill template kickback payouts.""" - templateKickbackBackfill(invoiceId: String!): Boolean! - - """Publishes a template.""" + "Publishes a template." templatePublish(id: String!, input: TemplatePublishInput!): Template! - - """Unpublishes a template.""" + "Unpublishes a template." templateUnpublish(id: String!): Boolean! - - """Updates a template.""" + "Updates a template." templateUpdate(id: String!, input: TemplateUpdateInput!): Template! - - """Setup 2FA authorization for authenticated user.""" + "Setup 2FA authorization for authenticated user." twoFactorInfoCreate(input: TwoFactorInfoCreateInput!): RecoveryCodes! - - """Deletes the TwoFactorInfo for the authenticated user.""" + "Deletes the TwoFactorInfo for the authenticated user." twoFactorInfoDelete: Boolean! - - """Reset the 2FA code for a user""" - twoFactorInfoReset(userId: String!): Boolean! - - """Generates the 2FA app secret for the authenticated user.""" + "Generates the 2FA app secret for the authenticated user." twoFactorInfoSecret: TwoFactorInfoSecret! - - """Validates the token for a 2FA action or for a login request.""" + "Validates the token for a 2FA action or for a login request." twoFactorInfoValidate(input: TwoFactorInfoValidateInput!): Boolean! - - """Allowlist a UsageAnomaly.""" - usageAnomalyAllow(input: UsageAnomalyAllowInput!): Boolean! - - """Ban a UsageAnomaly, along with all associated users.""" - usageAnomalyBan(input: UsageAnomalyBanInput!): Boolean! - - """Remove the usage limit for a customer""" + "Remove the usage limit for a customer" usageLimitRemove(input: UsageLimitRemoveInput!): Boolean! - - """Set the usage limit for a customer""" + "Set the usage limit for a customer" usageLimitSet(input: UsageLimitSetInput!): Boolean! - - """Ban a user""" - userBan(input: UserBanInput!): Boolean! - - """Unsubscribe from the Beta program.""" + "Unsubscribe from the Beta program." userBetaLeave: Boolean! - - """Delete the currently authenticated user""" + "Delete the currently authenticated user" userDelete: Boolean! - - """Disconnect your Railway account from Discord.""" + "Disconnect your Railway account from Discord." userDiscordDisconnect: Boolean! - - """Remove a flag on the user.""" + "Remove a flag on the user." userFlagsRemove(input: UserFlagsRemoveInput!): Boolean! - - """Set flags on the authenticated user.""" + "Set flags on the authenticated user." userFlagsSet(input: UserFlagsSetInput!): Boolean! - - """Update the riskLevel for a user""" - userRiskLevelUpdate(input: UserRiskLevelUpdateInput!): Boolean! - - """Update date of TermsAgreedOn""" + "Updates the profile for the authenticated user" + userProfileUpdate(input: UserProfileUpdateInput!): Boolean! + "Update date of TermsAgreedOn" userTermsUpdate: User - - """Unban a user""" - userUnban(userId: String!): Boolean! - - """Update currently logged in user""" + "Update currently logged in user" userUpdate(input: UserUpdateInput!): User - - """Upserts a collection of variables.""" + "Upserts a collection of variables." variableCollectionUpsert(input: VariableCollectionUpsertInput!): Boolean! - - """Deletes a variable.""" + "Deletes a variable." variableDelete(input: VariableDeleteInput!): Boolean! - - """Upserts a variable.""" + "Upserts a variable." variableUpsert(input: VariableUpsertInput!): Boolean! - - """Create a persistent volume in a project""" + "Create a persistent volume in a project" volumeCreate(input: VolumeCreateInput!): Volume! - - """Delete a persistent volume in a project""" + "Delete a persistent volume in a project" volumeDelete(volumeId: String!): Boolean! - - """ - Change the region of the volume instance. If the new region is different from the current region, a migration of the volume to the new region will be triggered, which will cause downtime for services that have this volume attached. - """ - volumeInstanceChangeRegion( - input: VolumeInstanceChangeRegionInput! - - """The id of the volume for which to change the region""" - volumeInstanceId: String! - ): Boolean! - - """ - Resize a volume instance. If no environmentId is provided, all volume instances for the volume will be resized. - """ - volumeInstanceResize( - """ - The environment of the volume instance to update. If null, all instances for the volume will be updated - """ - environmentId: String - input: VolumeInstanceResizeInput! - - """The id of the volume to resize""" - volumeId: String! - ): Boolean! - - """ - Update a volume instance. If no environmentId is provided, all volume instances for the volume will be updated. - """ + "Update a volume instance. If no environmentId is provided, all volume instances for the volume will be updated." volumeInstanceUpdate( - """ - The environment of the volume instance to update. If null, all instances for the volume will be updated - """ + "The environment of the volume instance to update. If null, all instances for the volume will be updated" environmentId: String input: VolumeInstanceUpdateInput! - - """The id of the volume to update""" + "The id of the volume to update" volumeId: String! ): Boolean! - - """Update a persistent volume in a project""" + "Update a persistent volume in a project" volumeUpdate(input: VolumeUpdateInput!, volumeId: String!): Volume! - - """Create a webhook on a project""" + "Create a webhook on a project" webhookCreate(input: WebhookCreateInput!): ProjectWebhook! - - """Delete a webhook from a project""" + "Delete a webhook from a project" webhookDelete(id: String!): Boolean! - - """Update a webhook on a project""" + "Update a webhook on a project" webhookUpdate(id: String!, input: WebhookUpdateInput!): ProjectWebhook! } - -interface Node { - id: ID! -} - type PageInfo { endCursor: String hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String } - type PaymentMethod { card: PaymentMethodCard id: String! } - type PaymentMethodCard { brand: String! country: String last4: String! } - -enum PlatformServiceKey { - ALL_PROVISIONS - ANON_PROVISIONS - FREE_PROVISIONS - NON_PRO_PROVISIONS - SIGNUPS -} - -enum PlatformServiceStatus { - DISABLE - ENABLE +type PlanLimitOverride implements Node { + config: SubscriptionPlanLimit! + id: ID! } - type PlatformStatus { incident: Incident isStable: Boolean! maintenance: Maintenance } - type Plugin implements Node { - containers(after: String, before: String, first: Int, last: Int): PluginContainersConnection! + containers( + after: String + before: String + first: Int + last: Int + ): PluginContainersConnection! createdAt: DateTime! deletedAt: DateTime + deprecatedAt: DateTime friendlyName: String! id: ID! logsEnabled: Boolean! + migrationDatabaseServiceId: String name: PluginType! project: Project! status: PluginStatus! - variables(after: String, before: String, first: Int, last: Int): PluginVariablesConnection! + variables( + after: String + before: String + first: Int + last: Int + ): PluginVariablesConnection! } - type PluginContainersConnection { edges: [PluginContainersConnectionEdge!]! pageInfo: PageInfo! } - type PluginContainersConnectionEdge { cursor: String! node: Container! } - -input PluginCreateInput { - environmentId: String - friendlyName: String - name: String! - projectId: String! -} - -input PluginRestartInput { - environmentId: String! -} - -enum PluginStatus { - LOCKED - REMOVED - RUNNING - STOPPED -} - -enum PluginType { - mongodb - mysql - postgresql - redis -} - -input PluginUpdateInput { - friendlyName: String! -} - type PluginVariablesConnection { edges: [PluginVariablesConnectionEdge!]! pageInfo: PageInfo! } - type PluginVariablesConnectionEdge { cursor: String! node: Variable! } - type Preferences implements Node { buildFailedEmail: Boolean! changelogEmail: Boolean! + communityEmail: Boolean! deployCrashedEmail: Boolean! id: ID! marketingEmail: Boolean! usageEmail: Boolean! } - -input PreferencesUpdateData { - buildFailedEmail: Boolean - changelogEmail: Boolean - deployCrashedEmail: Boolean - marketingEmail: Boolean - token: String - usageEmail: Boolean -} - type PrivateNetwork { createdAt: DateTime deletedAt: DateTime @@ -1612,14 +838,6 @@ type PrivateNetwork { publicId: String! tags: [String!]! } - -input PrivateNetworkCreateOrGetInput { - environmentId: String! - name: String! - projectId: String! - tags: [String!]! -} - type PrivateNetworkEndpoint { createdAt: DateTime deletedAt: DateTime @@ -1629,23 +847,29 @@ type PrivateNetworkEndpoint { serviceInstanceId: String! tags: [String!]! } - -input PrivateNetworkEndpointCreateOrGetInput { - environmentId: String! - privateNetworkId: String! - serviceId: String! - serviceName: String! - tags: [String!]! -} - type Project implements Node { baseEnvironment: Environment createdAt: DateTime! deletedAt: DateTime - deploymentTriggers(after: String, before: String, first: Int, last: Int): ProjectDeploymentTriggersConnection! - deployments(after: String, before: String, first: Int, last: Int): ProjectDeploymentsConnection! + deploymentTriggers( + after: String + before: String + first: Int + last: Int + ): ProjectDeploymentTriggersConnection! + deployments( + after: String + before: String + first: Int + last: Int + ): ProjectDeploymentsConnection! description: String - environments(after: String, before: String, first: Int, last: Int): ProjectEnvironmentsConnection! + environments( + after: String + before: String + first: Int + last: Int + ): ProjectEnvironmentsConnection! expiredAt: DateTime id: ID! isPublic: Boolean! @@ -1653,71 +877,69 @@ type Project implements Node { isUpdatable: Boolean! members: [ProjectMember!]! name: String! - plugins(after: String, before: String, first: Int, last: Int): ProjectPluginsConnection! + plugins( + after: String + before: String + first: Int + last: Int + ): ProjectPluginsConnection! prDeploys: Boolean! prForks: Boolean! - projectPermissions(after: String, before: String, first: Int, last: Int): ProjectProjectPermissionsConnection! - services(after: String, before: String, first: Int, last: Int): ProjectServicesConnection! - subscriptionType: String! + projectPermissions( + after: String + before: String + first: Int + last: Int + ): ProjectProjectPermissionsConnection! + services( + after: String + before: String + first: Int + last: Int + ): ProjectServicesConnection! + subscriptionPlanLimit: SubscriptionPlanLimit! + subscriptionType: SubscriptionPlanType! team: Team teamId: String updatedAt: DateTime! upstreamUrl: String - volumes(after: String, before: String, first: Int, last: Int): ProjectVolumesConnection! - webhooks(after: String, before: String, first: Int, last: Int): ProjectWebhooksConnection! -} - -input ProjectCreateInput { - defaultEnvironmentName: String - description: String - isPublic: Boolean - name: String - plugins: [String!] - prDeploys: Boolean - repo: ProjectCreateRepo - teamId: String -} - -input ProjectCreateRepo { - branch: String! - fullRepoName: String! + volumes( + after: String + before: String + first: Int + last: Int + ): ProjectVolumesConnection! + webhooks( + after: String + before: String + first: Int + last: Int + ): ProjectWebhooksConnection! } - type ProjectDeploymentTriggersConnection { edges: [ProjectDeploymentTriggersConnectionEdge!]! pageInfo: PageInfo! } - type ProjectDeploymentTriggersConnectionEdge { cursor: String! node: DeploymentTrigger! } - type ProjectDeploymentsConnection { edges: [ProjectDeploymentsConnectionEdge!]! pageInfo: PageInfo! } - type ProjectDeploymentsConnectionEdge { cursor: String! node: Deployment! } - type ProjectEnvironmentsConnection { edges: [ProjectEnvironmentsConnectionEdge!]! pageInfo: PageInfo! } - type ProjectEnvironmentsConnectionEdge { cursor: String! node: Environment! } - -input ProjectInviteUserInput { - email: String! - link: String! -} - type ProjectMember { avatar: String email: String! @@ -1725,68 +947,43 @@ type ProjectMember { name: String role: ProjectRole! } - -input ProjectMemberRemoveInput { - projectId: String! - userId: String! -} - -input ProjectMemberUpdateInput { - projectId: String! - role: ProjectRole! - userId: String! -} - type ProjectPermission implements Node { id: ID! projectId: String! role: ProjectRole! userId: String! } - type ProjectPluginsConnection { edges: [ProjectPluginsConnectionEdge!]! pageInfo: PageInfo! } - type ProjectPluginsConnectionEdge { cursor: String! node: Plugin! } - type ProjectProjectPermissionsConnection { edges: [ProjectProjectPermissionsConnectionEdge!]! pageInfo: PageInfo! } - type ProjectProjectPermissionsConnectionEdge { cursor: String! node: ProjectPermission! } - type ProjectResourceAccess { customDomain: AccessRule! + databaseDeployment: AccessRule! deployment: AccessRule! environment: AccessRule! plugin: AccessRule! } - -enum ProjectRole { - ADMIN - MEMBER - VIEWER -} - type ProjectServicesConnection { edges: [ProjectServicesConnectionEdge!]! pageInfo: PageInfo! } - type ProjectServicesConnectionEdge { cursor: String! node: Service! } - type ProjectToken implements Node { createdAt: DateTime! displayToken: String! @@ -1797,65 +994,28 @@ type ProjectToken implements Node { project: Project! projectId: String! } - -input ProjectTokenCreateInput { - environmentId: String! - name: String! - projectId: String! -} - -input ProjectTransferConfirmInput { - ownershipTransferId: String! - projectId: String! -} - -input ProjectTransferInitiateInput { - memberId: String! - projectId: String! -} - -input ProjectTransferToTeamInput { - teamId: String! -} - -input ProjectUpdateInput { - baseEnvironmentId: String - description: String - isPublic: Boolean - name: String - prDeploys: Boolean - - """[Experimental] Will be deprecated eventually""" - prForks: Boolean -} - type ProjectVolumesConnection { edges: [ProjectVolumesConnectionEdge!]! pageInfo: PageInfo! } - type ProjectVolumesConnectionEdge { cursor: String! node: Volume! } - type ProjectWebhook implements Node { id: ID! lastStatus: Int projectId: String! url: String! } - type ProjectWebhooksConnection { edges: [ProjectWebhooksConnectionEdge!]! pageInfo: PageInfo! } - type ProjectWebhooksConnectionEdge { cursor: String! node: ProjectWebhook! } - type ProviderAuth implements Node { email: String! id: ID! @@ -1863,419 +1023,312 @@ type ProviderAuth implements Node { provider: String! userId: String! } - type PublicStats { totalDeployments: Int! totalProjects: Int! totalUsers: Int! } - type Query { - """Gets all deployed containers for a environment+service for an admin.""" - adminAllContainerInfoForServiceInEnvironment(environmentId: String!, serviceId: String!): [ContainerInfo!]! - - """Gets all projects for an admin.""" - adminAllProjects(after: String, before: String, deleted: Boolean, expired: Boolean, filter: String, first: Int, last: Int): QueryAdminAllProjectsConnection! - - """Gets all services for an admin.""" - adminAllServices(after: String, before: String, deleted: Boolean, filter: String, first: Int, last: Int): QueryAdminAllServicesConnection! - - """Get all deployments for admin purposes""" - adminDeployments(after: String, before: String, first: Int, input: AdminDeploymentListInput!, last: Int): QueryAdminDeploymentsConnection! - - """Get admin stats. Primarily used for the admin dashboard.""" - adminStats: AdminStats! - - """Get all volume instances for a given volume""" + "Get all volume instances for a given volume" adminVolumeInstancesForVolume(volumeId: String!): [VolumeInstance!]! - - """Gets all API tokens for the authenticated user.""" - apiTokens(after: String, before: String, first: Int, last: Int): QueryApiTokensConnection! - - """Gets the ban reason history for a user.""" - banReasonHistory(after: String, before: String, first: Int, last: Int, userId: String!): QueryBanReasonHistoryConnection! - - """Fetch logs for a build""" + "Gets all API tokens for the authenticated user." + apiTokens( + after: String + before: String + first: Int + last: Int + ): QueryApiTokensConnection! + "Fetch logs for a build" buildLogs( deploymentId: String! endDate: DateTime - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int startDate: DateTime ): [Log!]! - - """Gets the image URL for a Notion image block""" + "Gets the image URL for a Notion image block" changelogBlockImage(id: String!): String! - - """Gets the history of changesets for an environment.""" - changesets(after: String, before: String, environmentId: String!, first: Int, last: Int, projectId: String!): QueryChangesetsConnection! - - """Fetch details for a custom domain""" + "Gets the history of changesets for an environment." + changesets( + after: String + before: String + environmentId: String! + first: Int + last: Int + projectId: String! + ): QueryChangesetsConnection! + "Fetch details for a custom domain" customDomain(id: String!, projectId: String!): CustomDomain! - - """Checks if a custom domain is available.""" + "Checks if a custom domain is available." customDomainAvailable(domain: String!): DomainAvailable! - - """Find a single deployment""" + "Find a single deployment" deployment(id: String!): Deployment! - - """Find a deployment by public url""" - deploymentByDomain(domain: String!): DeploymentByDomain! - - """Fetch logs for a deployment""" + "Fetch logs for a deployment" deploymentLogs( deploymentId: String! endDate: DateTime - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int startDate: DateTime ): [Log!]! - - """Get a short-lived URL to the deployment snapshot code""" - deploymentSnapshotCodeUri(deploymentId: String!): String! - - """All deployment triggers.""" - deploymentTriggers(after: String, before: String, environmentId: String!, first: Int, last: Int, projectId: String!, serviceId: String!): QueryDeploymentTriggersConnection! - - """Get all deployments""" - deployments(after: String, before: String, first: Int, input: DeploymentListInput!, last: Int): QueryDeploymentsConnection! - - """Returns basic information about the Railway Discord server""" - discordServerInfo: DiscordServerInfo! - - """Domain with status""" - domainStatus(id: String!, projectId: String!): DomainWithStatus! @deprecated(reason: "Use the `status` field within the `domain` query instead") - - """All domains for a service instance""" - domains(environmentId: String!, projectId: String!, serviceId: String!): AllDomains! - - """Find a single environment""" + "All deployment triggers." + deploymentTriggers( + after: String + before: String + environmentId: String! + first: Int + last: Int + projectId: String! + serviceId: String! + ): QueryDeploymentTriggersConnection! + "Get all deployments" + deployments( + after: String + before: String + first: Int + input: DeploymentListInput! + last: Int + ): QueryDeploymentsConnection! + "Domain with status" + domainStatus(id: String!, projectId: String!): DomainWithStatus! + @deprecated( + reason: "Use the `status` field within the `domain` query instead" + ) + "All domains for a service instance" + domains( + environmentId: String! + projectId: String! + serviceId: String! + ): AllDomains! + "Find a single environment" environment(id: String!): Environment! - - """ - Fetch logs for a project environment. Build logs are excluded unless a snapshot ID is explicitly provided in the filter - """ - environmentLogs( - """Latest date to look for logs after the anchor""" - afterDate: String - - """Limit the number of logs returned after the anchor""" - afterLimit: Int - - """Target date time to look for logs""" - anchorDate: String - - """Oldest date to look for logs before the anchor""" - beforeDate: String - - """Limit the number of logs returned before the anchor""" - beforeLimit: Int + "Get the patches for an environment" + environmentPatches( + after: String + before: String environmentId: String! - - """Filter logs using a query syntax""" - filter: String - ): [Log!]! - - """Gets all environments for a project.""" - environments(after: String, before: String, first: Int, isEphemeral: Boolean, last: Int, projectId: String!): QueryEnvironmentsConnection! - - """ - Get the estimated total cost of the project at the end of the current billing cycle - """ + first: Int + last: Int + ): QueryEnvironmentPatchesConnection! + "Gets all environments for a project." + environments( + after: String + before: String + first: Int + isEphemeral: Boolean + last: Int + projectId: String! + ): QueryEnvironmentsConnection! + "Get the estimated total cost of the project at the end of the current billing cycle" estimatedUsage( - """Whether to include deleted projects in estimations.""" + "Whether to include deleted projects in estimations." includeDeleted: Boolean measurements: [MetricMeasurement!]! projectId: String teamId: String userId: String ): [EstimatedUsage!]! - - """Gets the events for a project.""" - events(after: String, before: String, environmentId: String, first: Int, last: Int, projectId: String!): QueryEventsConnection! - - """Get GitHub events for a user""" - githubEvents(userId: String!): [GitHubEvent!]! - - """Check if a repo name is available""" - githubIsRepoNameAvailable(fullRepoName: String!): Boolean! - - """ - Get branches for a GitHub repo that the authenticated user has access to - """ - githubRepoBranches(owner: String!, repo: String!): [GitHubBranch!]! - - """Get a list of repos for a user that Railway has access to""" + "Gets the events for a project." + events( + after: String + before: String + environmentId: String + first: Int + last: Int + projectId: String! + ): QueryEventsConnection! + "Check if a repo name is available" + githubIsRepoNameAvailable(fullRepoName: String!): Boolean! + "Get branches for a GitHub repo that the authenticated user has access to" + githubRepoBranches(owner: String!, repo: String!): [GitHubBranch!]! + "Get a list of repos for a user that Railway has access to" githubRepos: [GitHubRepo!]! - - """Get a list of scopes the user has installed the installation to""" + "Get a list of scopes the user has installed the installation to" githubWritableScopes: [String!]! - - """Returns a list of usage anomalies grouped by service.""" - groupedUsageAnomalies(input: GroupedUsageAnomaliesInput!): [GroupedUsageAnomaly!]! - - """Get the Herokus apps for the current user""" + "Get the Herokus apps for the current user" herokuApps: [HerokuApp!]! - - """Get an integration auth by provider providerId""" + "Get an integration auth by provider providerId" integrationAuth(provider: String!, providerId: String!): IntegrationAuth! - - """Get all integration auths for a user""" - integrationAuths(after: String, before: String, first: Int, last: Int): QueryIntegrationAuthsConnection! - - """Get all integrations for a project""" - integrations(after: String, before: String, first: Int, last: Int, projectId: String!): QueryIntegrationsConnection! - - """Get an invite code by the code""" + "Get all integration auths for a user" + integrationAuths( + after: String + before: String + first: Int + last: Int + ): QueryIntegrationAuthsConnection! + "Get all integrations for a project" + integrations( + after: String + before: String + first: Int + last: Int + projectId: String! + ): QueryIntegrationsConnection! + "Get an invite code by the code" inviteCode(code: String!): InviteCode! - - """Returns the current lockdown status of the platform.""" - lockdownStatus: LockdownStatus! - - """Gets the authenticated user.""" + "Gets the authenticated user." me: User! - - """Get metrics for a project, environment, and service""" + "Get metrics for a project, environment, and service" metrics( - """ - The averaging window when computing CPU usage. By default, it is the same as the `sampleRateSeconds`. - """ + "The averaging window when computing CPU usage. By default, it is the same as the `sampleRateSeconds`." averagingWindowSeconds: Int - - """ - The end of the period to get metrics for. If not provided, the current datetime is used. - """ + "The end of the period to get metrics for. If not provided, the current datetime is used." endDate: DateTime environmentId: String - - """ - What to group the aggregated usage by. By default, it is grouped over the entire project. - """ + "What to group the aggregated usage by. By default, it is grouped over the entire project." groupBy: [MetricTag!] - - """Whether or not to include deleted projects in the results""" + "Whether or not to include deleted projects in the results" includeDeleted: Boolean measurements: [MetricMeasurement!]! pluginId: String projectId: String - - """ - The frequency of data points in the response. If the `sampleRateSeconds` is 60, then the response will contain one data point per minute. - """ + "The frequency of data points in the response. If the `sampleRateSeconds` is 60, then the response will contain one data point per minute." sampleRateSeconds: Int serviceId: String - - """The start of the period to get metrics for.""" + "The start of the period to get metrics for." startDate: DateTime! teamId: String userId: String volumeId: String ): [MetricsResult!]! - - """Get a collection in a MongoDB container""" - mongoCollectionData(database: String!, environmentId: String!, name: String!, pluginId: String, serviceId: String): MongoCollection! - - """Get a list of collection names in a MongoDB container""" - mongoCollectionNames(database: String!, environmentId: String!, pluginId: String, serviceId: String): [String!]! - - """Get a list of database names in a MongoDB container""" - mongoDatabaseNames(environmentId: String!, pluginId: String, serviceId: String): [String!]! - - """""" + "" node(id: ID!): Node - - """""" + "" nodes(ids: [ID!]!): [Node]! - - """Get a user's Plain Customer ID given their Discord ID.""" + "Get a user's Plain Customer ID given their Discord ID." plainCustomerIdForDiscordId(discordId: String!): String! - - """Get a user JWT token for a Discord id""" + "Get a user JWT token for a Discord id" plainJWTForDiscordId(discordId: String!): String! - - """Get the current status of the platform""" + "Get the current status of the platform" platformStatus: PlatformStatus! - - """Get a plugin by ID.""" + "Get a plugin by ID." plugin(id: String!): Plugin! - - """Fetch logs for a plugin""" + "Fetch logs for a plugin" pluginLogs( endDate: DateTime environmentId: String! - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int pluginId: String! startDate: DateTime ): [Log!]! - - """Get the email preferences for a user""" + "Get the email preferences for a user" preferences(token: String): Preferences! - - """Get a private network endpoint for a service instance.""" - privateNetworkEndpoint(environmentId: String!, privateNetworkId: String!, serviceId: String!): PrivateNetworkEndpoint - - """Check if an endpoint name is available.""" - privateNetworkEndpointNameAvailable(environmentId: String!, prefix: String!, privateNetworkId: String!): Boolean! - - """List private networks for an environment.""" + "Get a private network endpoint for a service instance." + privateNetworkEndpoint( + environmentId: String! + privateNetworkId: String! + serviceId: String! + ): PrivateNetworkEndpoint + "Check if an endpoint name is available." + privateNetworkEndpointNameAvailable( + environmentId: String! + prefix: String! + privateNetworkId: String! + ): Boolean! + "List private networks for an environment." privateNetworks(environmentId: String!): [PrivateNetwork!]! - - """Get a project by ID""" + "Get a project by ID" project(id: String!): Project! - - """Get an invite code for a project for a specifc role""" + "Get an invite code for a project for a specifc role" projectInviteCode(projectId: String!, role: ProjectRole!): InviteCode! - - """Gets users who belong to a project along with their role""" + "Gets users who belong to a project along with their role" projectMembers(projectId: String!): [ProjectMember!]! - - """Get resource access rules for project-specific actions""" + "Get resource access rules for project-specific actions" projectResourceAccess(projectId: String!): ProjectResourceAccess! - - """Get a single project token by the value in the header""" + "Get a single project token by the value in the header" projectToken: ProjectToken! - - """Get all project tokens for a project""" - projectTokens(after: String, before: String, first: Int, last: Int, projectId: String!): QueryProjectTokensConnection! - - """Gets all projects for a user or a team.""" - projects(after: String, before: String, first: Int, includeDeleted: Boolean, last: Int, teamId: String, userId: String): QueryProjectsConnection! - - """Get public Railway stats. Primarily used for the landing page.""" + "Get all project tokens for a project" + projectTokens( + after: String + before: String + first: Int + last: Int + projectId: String! + ): QueryProjectTokensConnection! + "Gets all projects for a user or a team." + projects( + after: String + before: String + first: Int + includeDeleted: Boolean + last: Int + teamId: String + userId: String + ): QueryProjectsConnection! + "Get public Railway stats. Primarily used for the landing page." publicStats: PublicStats! - - """Get data for key in a Redis container""" - redisGetKey(environmentId: String!, key: String!, pluginId: String, serviceId: String): JSON! - - """Get a list of keys in a Redis container""" - redisKeys(environmentId: String!, pluginId: String, serviceId: String): [RedisKey!]! - - """Gets the ReferralInfo for the authenticated user.""" + "Gets the ReferralInfo for the authenticated user." referralInfo: ReferralInfo! - - """List available regions""" + "List available regions" regions: [Region!]! - - """Get resource access for the current user or team""" + "Get resource access for the current user or team" resourceAccess(explicitResourceOwner: ExplicitOwnerInput): ResourceAccess! - - """Get a service by ID""" + "Get a service by ID" service(id: String!): Service! - - """Checks if a service domain is available""" + "Checks if a service domain is available" serviceDomainAvailable(domain: String!): DomainAvailable! - - """Get a service instance belonging to a service and environment""" + "Get a service instance belonging to a service and environment" serviceInstance(environmentId: String!, serviceId: String!): ServiceInstance! - - """Check if the upstream repo for a service has an update available""" - serviceInstanceIsUpdatable(environmentId: String!, serviceId: String!): Boolean! - - """Gets all sessions for authenticated user.""" - sessions(after: String, before: String, first: Int, last: Int): QuerySessionsConnection! - - """Get rows for a SQL table""" - sqlTable(databaseType: String!, environmentId: String!, limit: Int, name: String!, offset: Int, pluginId: String, serviceId: String): SQLTable! - - """Get a list of table names in SQLQL container""" - sqlTableNames(databaseType: String!, environmentId: String!, pluginId: String, serviceId: String): [String!]! - - """All TCP proxies for a service instance""" + "Check if the upstream repo for a service has an update available" + serviceInstanceIsUpdatable( + environmentId: String! + serviceId: String! + ): Boolean! + "Gets all sessions for authenticated user." + sessions( + after: String + before: String + first: Int + last: Int + ): QuerySessionsConnection! + "All TCP proxies for a service instance" tcpProxies(environmentId: String!, serviceId: String!): [TCPProxy!]! - - """Find a team by ID""" + "Find a team by ID" team(id: String!): Team! - - """Find a team by invite code""" + "Find a team by invite code" teamByCode(code: String!): Team! - - """ - Fetch Discord info associated with Direct Support-eligible team members, given a Discord UID - """ - teamDirectSupportDiscordInfoForDiscordId(discordId: String!): TeamDirectSupportDiscordInfo - - """Get all templates for a team.""" - teamTemplates(after: String, before: String, first: Int, last: Int, teamId: String!): QueryTeamTemplatesConnection! - - """Get all teams""" - teams(after: String, before: String, filter: String, first: Int, last: Int, state: String): QueryTeamsConnection! - - """Get a template by code or GitHub owner and repo.""" + "Fetch Discord info associated with Direct Support-eligible team members, given a Discord UID" + teamDirectSupportDiscordInfoForDiscordId( + discordId: String! + ): TeamDirectSupportDiscordInfo + "Get all templates for a team." + teamTemplates( + after: String + before: String + first: Int + last: Int + teamId: String! + ): QueryTeamTemplatesConnection! + "Get a template by code or GitHub owner and repo." template(code: String, owner: String, repo: String): Template! - - """ - Convert a Heroku template to a (legacy) Railway template config object. - """ + "Convert a Heroku template to a (legacy) Railway template config object." templateFromHerokuTemplate(repoUrl: String!): JSON! - - """Gets the README for a template.""" - templateReadme(code: String!): TemplateReadme! - - """Get the source template for a project.""" + "Get the source template for a project." templateSourceForProject(projectId: String!): Template - - """Get all published templates.""" + "Get all published templates." templates( after: String before: String first: Int last: Int - - """If set to true, only recommended templates will be returned.""" + "If set to true, only recommended templates will be returned." recommended: Boolean ): QueryTemplatesConnection! - - """Get the top projects by a metric""" - topMetrics( - """ - The averaging window when computing CPU usage. By default, it is the same as the `sampleRateSeconds`. - """ - averagingWindowSeconds: Int - - """ - The end of the period to get metrics for. If not provided, the current datetime is used. - """ - endDate: DateTime - limit: Int! - measurement: MetricMeasurement! - - """ - The frequency of data points in the response. If the `sampleRateSeconds` is 60, then the response will contain one data point per minute. - """ - sampleRateSeconds: Int - - """The start of the period to get metrics for.""" - startDate: DateTime! - ): TopMetricsResult! - - """Gets the TwoFactorInfo for the authenticated user.""" + "Gets the TwoFactorInfo for the authenticated user." twoFactorInfo: TwoFactorInfo! - - """ - Get the usage for a single project or all projects for a user/team. If no `projectId` or `teamId` is provided, the usage for the current user is returned. - """ + "Get the usage for a single project or all projects for a user/team. If no `projectId` or `teamId` is provided, the usage for the current user is returned." usage( endDate: DateTime - - """ - What to group the aggregated usage by. By default, it is grouped over the entire project. - """ + "What to group the aggregated usage by. By default, it is grouped over the entire project." groupBy: [MetricTag!] - - """Whether to include deleted projects in the usage.""" + "Whether to include deleted projects in the usage." includeDeleted: Boolean measurements: [MetricMeasurement!]! projectId: String @@ -2283,425 +1336,236 @@ type Query { teamId: String userId: String ): [AggregatedUsage!]! - - """Get a user by id""" - user(userId: String!): User! - - """Get the user id corresponding to a Discord id""" + "Get the user id corresponding to a Discord id" userIdForDiscordId(discordId: String!): String! - - """Get the reasoning behind the risk level of a user""" - userRiskLevel(userId: String!): RiskLevelDetails! - - """Get all templates for the current user.""" - userTemplates(after: String, before: String, first: Int, last: Int): QueryUserTemplatesConnection! - - """Get all users""" - users(after: String, before: String, first: Int, input: UsersFilterInput, last: Int): QueryUsersConnection! - - """Gets all users with same IP as specified user.""" - usersWithClashingIP(userId: String!): [UserWithClashingIP!]! - - """ - All variables by pluginId or serviceId. If neither are provided, all shared variables are returned. - """ + "Get the public profile for a user" + userProfile(username: String!): UserProfileResponse! + "Get all templates for the current user." + userTemplates( + after: String + before: String + first: Int + last: Int + ): QueryUserTemplatesConnection! + "All variables by pluginId or serviceId. If neither are provided, all shared variables are returned." variables( environmentId: String! - - """Provide a pluginId to get all variables for a specific plugin.""" + "Provide a pluginId to get all variables for a specific plugin." pluginId: String projectId: String! - - """Provide a serviceId to get all variables for a specific service.""" + "Provide a serviceId to get all variables for a specific service." serviceId: String unrendered: Boolean ): ServiceVariables! - - """All rendered variables that are required for a service deployment.""" - variablesForServiceDeployment(environmentId: String!, projectId: String!, serviceId: String!): ServiceVariables! - - """Get information about the user's Vercel accounts""" + "All rendered variables that are required for a service deployment." + variablesForServiceDeployment( + environmentId: String! + projectId: String! + serviceId: String! + ): ServiceVariables! + "Get information about the user's Vercel accounts" vercelInfo: VercelInfo! - - """Get all webhooks for a project""" - webhooks(after: String, before: String, first: Int, last: Int, projectId: String!): QueryWebhooksConnection! - - """Gets the status of a workflow""" - workflowStatus(projectId: String, workflowId: String!): WorkflowResult! -} - -type QueryAdminAllProjectsConnection { - edges: [QueryAdminAllProjectsConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryAdminAllProjectsConnectionEdge { - cursor: String! - node: Project! -} - -type QueryAdminAllServicesConnection { - edges: [QueryAdminAllServicesConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryAdminAllServicesConnectionEdge { - cursor: String! - node: Service! -} - -type QueryAdminDeploymentsConnection { - edges: [QueryAdminDeploymentsConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryAdminDeploymentsConnectionEdge { - cursor: String! - node: Deployment! + "Get a single volume instance by id" + volumeInstance(id: String!): VolumeInstance! + "Get all webhooks for a project" + webhooks( + after: String + before: String + first: Int + last: Int + projectId: String! + ): QueryWebhooksConnection! + "Gets the status of a workflow" + workflowStatus(workflowId: String!): WorkflowResult! } - type QueryApiTokensConnection { edges: [QueryApiTokensConnectionEdge!]! pageInfo: PageInfo! } - type QueryApiTokensConnectionEdge { cursor: String! node: ApiToken! } - -type QueryBanReasonHistoryConnection { - edges: [QueryBanReasonHistoryConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryBanReasonHistoryConnectionEdge { - cursor: String! - node: BanReasonHistory! -} - type QueryChangesetsConnection { edges: [QueryChangesetsConnectionEdge!]! pageInfo: PageInfo! } - type QueryChangesetsConnectionEdge { cursor: String! node: Changeset! } - type QueryDeploymentTriggersConnection { edges: [QueryDeploymentTriggersConnectionEdge!]! pageInfo: PageInfo! } - type QueryDeploymentTriggersConnectionEdge { cursor: String! node: DeploymentTrigger! } - type QueryDeploymentsConnection { edges: [QueryDeploymentsConnectionEdge!]! pageInfo: PageInfo! } - type QueryDeploymentsConnectionEdge { cursor: String! node: Deployment! } - +type QueryEnvironmentPatchesConnection { + edges: [QueryEnvironmentPatchesConnectionEdge!]! + pageInfo: PageInfo! +} +type QueryEnvironmentPatchesConnectionEdge { + cursor: String! +} type QueryEnvironmentsConnection { edges: [QueryEnvironmentsConnectionEdge!]! pageInfo: PageInfo! } - type QueryEnvironmentsConnectionEdge { cursor: String! node: Environment! } - type QueryEventsConnection { edges: [QueryEventsConnectionEdge!]! pageInfo: PageInfo! } - type QueryEventsConnectionEdge { cursor: String! node: Event! } - type QueryIntegrationAuthsConnection { edges: [QueryIntegrationAuthsConnectionEdge!]! pageInfo: PageInfo! } - type QueryIntegrationAuthsConnectionEdge { cursor: String! node: IntegrationAuth! } - type QueryIntegrationsConnection { edges: [QueryIntegrationsConnectionEdge!]! pageInfo: PageInfo! } - type QueryIntegrationsConnectionEdge { cursor: String! node: Integration! } - type QueryProjectTokensConnection { edges: [QueryProjectTokensConnectionEdge!]! pageInfo: PageInfo! } - type QueryProjectTokensConnectionEdge { cursor: String! node: ProjectToken! } - type QueryProjectsConnection { edges: [QueryProjectsConnectionEdge!]! pageInfo: PageInfo! } - type QueryProjectsConnectionEdge { cursor: String! node: Project! } - type QuerySessionsConnection { edges: [QuerySessionsConnectionEdge!]! pageInfo: PageInfo! } - type QuerySessionsConnectionEdge { cursor: String! node: Session! } - type QueryTeamTemplatesConnection { edges: [QueryTeamTemplatesConnectionEdge!]! pageInfo: PageInfo! } - type QueryTeamTemplatesConnectionEdge { cursor: String! node: Template! } - -type QueryTeamsConnection { - edges: [QueryTeamsConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryTeamsConnectionEdge { - cursor: String! - node: Team! -} - type QueryTemplatesConnection { edges: [QueryTemplatesConnectionEdge!]! pageInfo: PageInfo! } - type QueryTemplatesConnectionEdge { cursor: String! node: Template! } - type QueryUserTemplatesConnection { edges: [QueryUserTemplatesConnectionEdge!]! pageInfo: PageInfo! } - type QueryUserTemplatesConnectionEdge { cursor: String! node: Template! } - -type QueryUsersConnection { - edges: [QueryUsersConnectionEdge!]! - pageInfo: PageInfo! -} - -type QueryUsersConnectionEdge { - cursor: String! - node: User! -} - type QueryWebhooksConnection { edges: [QueryWebhooksConnectionEdge!]! pageInfo: PageInfo! } - type QueryWebhooksConnectionEdge { cursor: String! node: ProjectWebhook! } - -input RecoveryCodeValidateInput { - code: String! - twoFactorLinkingKey: String -} - type RecoveryCodes { recoveryCodes: [String!]! } - -type RedisKey { - name: String! - ttl: BigInt - type: String! -} - type ReferralInfo implements Node { code: String! id: ID! referralStats: ReferralStats! status: String! } - -input ReferralInfoUpdateInput { - code: String! -} - type ReferralStats { credited: Int! pending: Int! } - -enum ReferralStatus { - REFEREE_CREDITED - REFERRER_CREDITED - REGISTERED -} - type ReferralUser { code: String! id: String! status: ReferralStatus! } - -input RefundFormInput { - message: String! - stripeInvoiceId: String! - teamId: String -} - type Region { name: String! } - -enum RegistrationStatus { - ONBOARDED - REGISTERED - WAITLISTED -} - -input ResetPluginCredentialsInput { - environmentId: String! -} - -input ResetPluginInput { - environmentId: String! -} - type ResourceAccess { project: AccessRule! } - -enum ResourceOwnerType { - TEAM - USER -} - -enum RestartPolicyType { - ALWAYS - NEVER - ON_FAILURE -} - -type RiskLevelDetails { - compositeScore: CompositeScore! - fairUse: Boolean! - terms: Boolean! -} - -input SQLColumnInput { - constraint: String - default: String - name: String! - type: String! -} - -"""Response returned after running a raw query""" -type SQLRawQueryResponse { - raw: JSON! -} - -input SQLRowInput { - name: String! - value: String! -} - -type SQLTable { - columnNames: [String!]! - columnTypes: [Int!]! - name: String! - primaryKey: String! - rows: [JSON!]! - totalRows: Int! -} - type Service implements Node { createdAt: DateTime! deletedAt: DateTime - deployments(after: String, before: String, first: Int, last: Int): ServiceDeploymentsConnection! + deployments( + after: String + before: String + first: Int + last: Int + ): ServiceDeploymentsConnection! icon: String id: ID! name: String! project: Project! projectId: String! - repoTriggers(after: String, before: String, first: Int, last: Int): ServiceRepoTriggersConnection! - serviceInstances(after: String, before: String, first: Int, last: Int): ServiceServiceInstancesConnection! + repoTriggers( + after: String + before: String + first: Int + last: Int + ): ServiceRepoTriggersConnection! + serviceInstances( + after: String + before: String + first: Int + last: Int + ): ServiceServiceInstancesConnection! + templateThreadSlug: String updatedAt: DateTime! } - -input ServiceConnectInput { - """The branch to connect to. e.g. 'main'""" - branch: String - - """Name of the Dockerhub or GHCR image to connect this service to.""" - image: String - - """The full name of the repo to connect to. e.g. 'railwayapp/starters'""" - repo: String -} - -input ServiceCreateInput { - branch: String - - """ - [Experimental] Environment ID. If the specified environment is a fork, the service will only be created in it. Otherwise it will created in all environments that are not forks of other environments - """ - environmentId: String - name: String - projectId: String! - source: ServiceSourceInput - variables: ServiceVariables -} - type ServiceDeploymentsConnection { edges: [ServiceDeploymentsConnectionEdge!]! pageInfo: PageInfo! } - type ServiceDeploymentsConnectionEdge { cursor: String! node: Deployment! } - type ServiceDomain implements Domain { createdAt: DateTime deletedAt: DateTime @@ -2712,18 +1576,6 @@ type ServiceDomain implements Domain { suffix: String updatedAt: DateTime } - -input ServiceDomainCreateInput { - environmentId: String! - serviceId: String! -} - -input ServiceDomainUpdateInput { - domain: String! - environmentId: String! - serviceId: String! -} - type ServiceInstance implements Node { buildCommand: String builder: Builder! @@ -2752,67 +1604,27 @@ type ServiceInstance implements Node { upstreamUrl: String watchPatterns: [String!]! } - -input ServiceInstanceUpdateInput { - buildCommand: String - builder: Builder - cronSchedule: String - healthcheckPath: String - healthcheckTimeout: Int - nixpacksPlan: JSON - numReplicas: Int - railwayConfigFile: String - region: String - restartPolicyMaxRetries: Int - restartPolicyType: RestartPolicyType - rootDirectory: String - sleepApplication: Boolean - source: ServiceSourceInput - startCommand: String - watchPatterns: [String!] -} - type ServiceRepoTriggersConnection { edges: [ServiceRepoTriggersConnectionEdge!]! pageInfo: PageInfo! } - type ServiceRepoTriggersConnectionEdge { cursor: String! node: DeploymentTrigger! } - type ServiceServiceInstancesConnection { edges: [ServiceServiceInstancesConnectionEdge!]! pageInfo: PageInfo! } - type ServiceServiceInstancesConnectionEdge { cursor: String! node: ServiceInstance! } - type ServiceSource { image: String repo: String template: TemplateServiceSource } - -input ServiceSourceInput { - image: String - repo: String -} - -input ServiceUpdateInput { - icon: String - name: String -} - -""" -The ServiceVariables scalar type represents values as the TypeScript type: Record. Example: "{ foo: 'bar', baz: 'qux' }" -""" -scalar ServiceVariables - type Session implements Node { createdAt: DateTime! expiredAt: DateTime! @@ -2822,109 +1634,54 @@ type Session implements Node { type: SessionType! updatedAt: DateTime! } - -enum SessionType { - BROWSER - CLI -} - -input SharedVariableConfigureInput { - disabledServiceIds: [String!]! - enabledServiceIds: [String!]! - environmentId: String! - name: String! - projectId: String! -} - type SimilarTemplate { code: String! createdAt: DateTime! creator: TemplateCreator deploys: Int! description: String + health: Float image: String name: String! teamId: String userId: String } - type Subscription { - """Stream logs for a build""" + "Stream logs for a build" buildLogs( deploymentId: String! - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int ): [Log!]! - - """Stream logs for a deployment""" + "Stream logs for a deployment" deploymentLogs( deploymentId: String! - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int ): [Log!]! - - """Stream logs for a project environment""" - environmentLogs( - """Latest date to look for logs after the anchor""" - afterDate: String - - """Limit the number of logs returned after the anchor""" - afterLimit: Int - - """Target date time to look for logs""" - anchorDate: String - - """Oldest date to look for logs before the anchor""" - beforeDate: String - - """Limit the number of logs returned before the anchor""" - beforeLimit: Int - environmentId: String! - - """Filter logs using a query syntax""" - filter: String - ): [Log!]! - - """Stream logs for a plugin""" + "Stream logs for a plugin" pluginLogs( environmentId: String! - - """Filter logs by a string. Providing an empty value will match all logs.""" + "Filter logs by a string. Providing an empty value will match all logs." filter: String - - """Limit the number of logs returned (defaults 100, max 5000)""" + "Limit the number of logs returned (defaults 100, max 5000)" limit: Int pluginId: String! ): [Log!]! } - type SubscriptionDiscount { couponId: String! } - type SubscriptionItem { itemId: String! priceId: String! productId: String! quantity: BigInt } - -enum SubscriptionState { - ACTIVE - CANCELLED - INACTIVE - PAST_DUE - UNPAID -} - type TCPProxy { applicationPort: Int! createdAt: DateTime @@ -2936,23 +1693,8 @@ type TCPProxy { serviceId: String! updatedAt: DateTime } - -input TCPProxyCreateInput { - applicationPort: Int! - environmentId: String! - serviceId: String! -} - -input TalkToAnEngineerFormInput { - deploymentId: String = null - environmentId: String = null - message: String! - projectId: String = null - serviceId: String = null - teamId: String! -} - type Team implements Node { + adoptionLevel: Float! avatar: String banReason: String createdAt: DateTime! @@ -2962,47 +1704,25 @@ type Team implements Node { isEligibleForDirectSupport: Boolean! members: [TeamMember!]! name: String! - projects(after: String, before: String, first: Int, last: Int): TeamProjectsConnection! + projects( + after: String + before: String + first: Int + last: Int + ): TeamProjectsConnection! teamPermissions: [TeamPermission!]! updatedAt: DateTime! } - -input TeamBanInput { - banReason: String! -} - -input TeamBulkProjectTransferInput { - projectIds: [String!]! - teamId: String! -} - -input TeamCreateAndSubscribeInput { - avatar: String - name: String! - paymentMethodId: String! -} - type TeamCreateAndSubscribeResponse { customerId: String! paymentIntent: JSON teamId: String! } - -input TeamCreateInput { - avatar: String - name: String! -} - type TeamDirectSupportDiscordInfo { memberDiscordIds: [String!]! teamId: String! teamName: String! } - -input TeamInviteCodeCreateInput { - role: String! -} - type TeamMember { avatar: String email: String! @@ -3010,7 +1730,6 @@ type TeamMember { name: String role: TeamRole! } - type TeamPermission implements Node { createdAt: DateTime! id: ID! @@ -3019,140 +1738,49 @@ type TeamPermission implements Node { updatedAt: DateTime! userId: String! } - -input TeamPermissionChangeInput { - role: TeamRole! - teamId: String! - userId: String! -} - type TeamProjectsConnection { edges: [TeamProjectsConnectionEdge!]! pageInfo: PageInfo! } - type TeamProjectsConnectionEdge { cursor: String! node: Project! } - -input TeamResourcesStopInput { - reason: String! -} - -enum TeamRole { - ADMIN - MEMBER -} - -input TeamUpdateInput { - avatar: String - name: String! -} - -input TeamUserInviteInput { - code: String! - email: String! -} - -input TeamUserRemoveInput { - userId: String! -} - -input TelemetrySendInput { - command: String! - environmentId: String - error: String! - projectId: String - stacktrace: String! - version: String -} - type Template implements Node { activeProjects: Int! code: String! + communityThreadSlug: String config: TemplateConfig! createdAt: DateTime! creator: TemplateCreator demoProjectId: String + health: Float id: ID! isApproved: Boolean! metadata: TemplateMetadata! projects: Int! - services(after: String, before: String, first: Int, last: Int): TemplateServicesConnection! + services( + after: String + before: String + first: Int + last: Int + ): TemplateServicesConnection! similarTemplates: [SimilarTemplate!]! status: TemplateStatus! teamId: String - totalPayout: Int! + totalPayout: Float! userId: String } - -scalar TemplateConfig - -input TemplateCreateInput { - config: TemplateConfig! - demoProjectId: String - metadata: TemplateMetadata! - services: [TemplateServiceCreateInput!]! - teamId: String -} - type TemplateCreator { avatar: String + hasPublicProfile: Boolean! name: String + username: String } - -input TemplateDeployInput { - plugins: [String!] - projectId: String - services: [TemplateDeployService!]! - teamId: String - templateCode: String -} - type TemplateDeployPayload { projectId: String! workflowId: String } - -input TemplateDeployService { - commit: String - hasDomain: Boolean - healthcheckPath: String - id: String - isPrivate: Boolean - name: String - owner: String - rootDirectory: String - serviceIcon: String - serviceName: String! - startCommand: String - tcpProxyApplicationPort: Int - template: String! - variables: ServiceVariables - volumes: [TemplateVolume!] -} - -input TemplateGenerateInput { - projectId: String! -} - -scalar TemplateMetadata - -input TemplatePublishInput { - category: String! - description: String! - image: String - readme: String! - teamId: String -} - -type TemplateReadme { - description: String - name: String! - readmeContent: String! -} - type TemplateService implements Node { config: TemplateServiceConfig! createdAt: DateTime! @@ -3160,91 +1788,26 @@ type TemplateService implements Node { templateId: String! updatedAt: DateTime! } - -scalar TemplateServiceConfig - -input TemplateServiceCreateInput { - config: TemplateServiceConfig! -} - type TemplateServiceSource { serviceName: String! serviceSource: String! } - -input TemplateServiceUpdateInput { - config: TemplateServiceConfig! - id: String -} - type TemplateServicesConnection { edges: [TemplateServicesConnectionEdge!]! pageInfo: PageInfo! } - type TemplateServicesConnectionEdge { cursor: String! node: TemplateService! } - -enum TemplateStatus { - HIDDEN - PUBLISHED - UNPUBLISHED -} - -input TemplateUpdateInput { - config: TemplateConfig! - demoProjectId: String - - """An admin-only flag to force-update a template.""" - forceUpdate: Boolean = false - metadata: TemplateMetadata! - services: [TemplateServiceUpdateInput!]! - teamId: String -} - -scalar TemplateVolume - -input TogglePlatformServiceInput { - platformServiceKey: PlatformServiceKey! - reason: String - status: PlatformServiceStatus! -} - -"""The result of a top metrics query.""" -type TopMetricsResult { - metrics: [MetricsResult!]! - projects: [Project!]! -} - -type TotalUsage { - current: Float - estimated: Float -} - type TwoFactorInfo { hasRecoveryCodes: Boolean! isVerified: Boolean! } - -input TwoFactorInfoCreateInput { - token: String! -} - type TwoFactorInfoSecret { secret: String! uri: String! } - -input TwoFactorInfoValidateInput { - token: String! - twoFactorLinkingKey: String -} - -"""The `Upload` scalar type represents a file upload.""" -scalar Upload - type UsageAnomaly implements Node { actedOn: DateTime action: UsageAnomalyAction @@ -3253,48 +1816,12 @@ type UsageAnomaly implements Node { flaggedFor: UsageAnomalyFlagReason! id: ID! } - -"""Possible actions for a UsageAnomaly.""" -enum UsageAnomalyAction { - ALLOWED - AUTOBANNED - BANNED -} - -input UsageAnomalyAllowInput { - usageAnomalyIds: [String!]! -} - -input UsageAnomalyBanInput { - teamId: String - usageAnomalyIds: [String!]! - userIds: [String!]! -} - -"""Possible flag reasons for a UsageAnomaly.""" -enum UsageAnomalyFlagReason { - HIGH_CPU_USAGE - HIGH_DISK_USAGE - HIGH_NETWORK_USAGE -} - type UsageLimit implements Node { customerId: String! hardLimit: Int id: ID! softLimit: Int! } - -input UsageLimitRemoveInput { - customerId: String! -} - -input UsageLimitSetInput { - customerId: String! - hardLimitDollars: Int - softLimitDollars: Int! -} - type User implements Node { agreedFairUse: Boolean! avatar: String @@ -3314,94 +1841,73 @@ type User implements Node { isVerified: Boolean! lastLogin: DateTime! name: String - projects(after: String, before: String, first: Int, last: Int): UserProjectsConnection! - providerAuths(after: String, before: String, first: Int, last: Int): UserProviderAuthsConnection! + profile: UserProfile + projects( + after: String + before: String + first: Int + last: Int + ): UserProjectsConnection! + providerAuths( + after: String + before: String + first: Int + last: Int + ): UserProviderAuthsConnection! referredUsers: [ReferralUser!]! registrationStatus: RegistrationStatus! riskLevel: Float - teams(after: String, before: String, first: Int, last: Int): UserTeamsConnection! + teams( + after: String + before: String + first: Int + last: Int + ): UserTeamsConnection! termsAgreedOn: DateTime + username: String } - -input UserBanInput { - reason: String! - userId: String! -} - type UserCost { current: Float! estimated: Float! } - -enum UserFlag { - BETA -} - -input UserFlagsRemoveInput { - flags: [UserFlag!]! - userId: String +type UserProfile { + bio: String + isPublic: Boolean! + website: String } - -input UserFlagsSetInput { - flags: [UserFlag!]! - userId: String +type UserProfileResponse { + avatar: String + createdAt: DateTime! + name: String + profile: UserProfile! + publishedTemplates: [SimilarTemplate!]! + totalDeploys: Int! + username: String } - type UserProjectsConnection { edges: [UserProjectsConnectionEdge!]! pageInfo: PageInfo! } - type UserProjectsConnectionEdge { cursor: String! node: Project! } - type UserProviderAuthsConnection { edges: [UserProviderAuthsConnectionEdge!]! pageInfo: PageInfo! } - type UserProviderAuthsConnectionEdge { cursor: String! node: ProviderAuth! } - -input UserRiskLevelUpdateInput { - riskLevel: Float - userId: String! -} - type UserTeamsConnection { edges: [UserTeamsConnectionEdge!]! pageInfo: PageInfo! } - type UserTeamsConnectionEdge { cursor: String! node: Team! } - -input UserUpdateInput { - avatar: String - name: String -} - -type UserWithClashingIP { - email: String! - id: String! - isBanned: Boolean! -} - -input UsersFilterInput { - admin: Boolean - banned: Boolean - filter: String - referredUsers: Boolean - riskLevel: Float - usageSubscription: Boolean -} - type Variable implements Node { createdAt: DateTime! environment: Environment! @@ -3415,34 +1921,6 @@ type Variable implements Node { serviceId: String updatedAt: DateTime! } - -input VariableCollectionUpsertInput { - environmentId: String! - projectId: String! - - """ - When set to true, removes all existing variables before upserting the new collection. - """ - replace: Boolean = false - serviceId: String - variables: ServiceVariables! -} - -input VariableDeleteInput { - environmentId: String! - name: String! - projectId: String! - serviceId: String -} - -input VariableUpsertInput { - environmentId: String! - name: String! - projectId: String! - serviceId: String - value: String! -} - type VercelAccount { id: String! integrationAuthId: String! @@ -3451,39 +1929,27 @@ type VercelAccount { projects: [VercelProject!]! slug: String } - type VercelInfo { accounts: [VercelAccount!]! } - type VercelProject { accountId: String! id: String! name: String! } - type Volume implements Node { createdAt: DateTime! id: ID! name: String! project: Project! projectId: String! - volumeInstances(after: String, before: String, first: Int, last: Int): VolumeVolumeInstancesConnection! -} - -input VolumeCreateInput { - """The path in the container to mount the volume to""" - mountPath: String! - - """The project to create the volume in""" - projectId: String! - - """ - The service to attach the volume to. If not provided, the volume will be disconnected. - """ - serviceId: String + volumeInstances( + after: String + before: String + first: Int + last: Int + ): VolumeVolumeInstancesConnection! } - type VolumeInstance implements Node { createdAt: DateTime! currentSizeMB: Float! @@ -3496,82 +1962,711 @@ type VolumeInstance implements Node { service: Service! serviceId: String sizeMB: Int! - stacker: String state: VolumeState volume: Volume! volumeId: String! } - -input VolumeInstanceChangeRegionInput { - """ - The region of the volume instance. If provided and different from the current region, a migration of the volume to the new region will be triggered, which will cause downtime for services that have this volume attached. - """ - region: String! -} - -input VolumeInstanceResizeInput { - """ - The size of the volume instance in MB. You can only resize a volume upwards - """ - targetSizeMB: Int! -} - -input VolumeInstanceUpdateInput { - """ - The mount path of the volume instance. If not provided, the mount path will not be updated. - """ - mountPath: String - - """ - The service to attach the volume to. If not provided, the volume will be disconnected. - """ - serviceId: String - - """ - The state of the volume instance. If not provided, the state will not be updated. - """ - state: VolumeState -} - -enum VolumeState { - DELETED - DELETING - ERROR - MIGRATING - READY - UPDATING -} - -input VolumeUpdateInput { - """The name of the volume""" - name: String -} - type VolumeVolumeInstancesConnection { edges: [VolumeVolumeInstancesConnectionEdge!]! pageInfo: PageInfo! } - type VolumeVolumeInstancesConnectionEdge { cursor: String! node: VolumeInstance! } - -input WebhookCreateInput { - projectId: String! - url: String! -} - -input WebhookUpdateInput { - url: String! -} - type WorkflowResult { + error: String status: WorkflowStatus! } - -enum WorkflowStatus { - Complete - Error - Running -} \ No newline at end of file +interface Domain { + createdAt: DateTime + deletedAt: DateTime + domain: String! + environmentId: String! + id: ID! + serviceId: String! + updatedAt: DateTime +} +interface Node { + id: ID! +} +enum ActiveFeatureFlag { + SERVICE_GROUPS + STACKER_WORKFLOWS + TEMPLATE_COMPOSER +} +enum Builder { + HEROKU + NIXPACKS + PAKETO +} +enum CDNProvider { + DETECTED_CDN_PROVIDER_CLOUDFLARE + DETECTED_CDN_PROVIDER_UNSPECIFIED + UNRECOGNIZED +} +enum CertificateStatus { + CERTIFICATE_STATUS_TYPE_ISSUE_FAILED + CERTIFICATE_STATUS_TYPE_ISSUING + CERTIFICATE_STATUS_TYPE_UNSPECIFIED + CERTIFICATE_STATUS_TYPE_VALID + UNRECOGNIZED +} +enum CnameCheckStatus { + ERROR + INFO + INVALID + VALID + WAITING +} +enum CreditType { + APPLIED + CREDIT + DEBIT + STRIPE + WAIVED +} +enum DNSRecordPurpose { + DNS_RECORD_PURPOSE_ACME_DNS01_CHALLENGE + DNS_RECORD_PURPOSE_TRAFFIC_ROUTE + DNS_RECORD_PURPOSE_UNSPECIFIED + UNRECOGNIZED +} +enum DNSRecordStatus { + DNS_RECORD_STATUS_PROPAGATED + DNS_RECORD_STATUS_REQUIRES_UPDATE + DNS_RECORD_STATUS_UNSPECIFIED + UNRECOGNIZED +} +enum DNSRecordType { + DNS_RECORD_TYPE_A + DNS_RECORD_TYPE_CNAME + DNS_RECORD_TYPE_NS + DNS_RECORD_TYPE_UNSPECIFIED + UNRECOGNIZED +} +enum DeploymentStatus { + BUILDING + CRASHED + DEPLOYING + FAILED + INITIALIZING + QUEUED + REMOVED + REMOVING + SKIPPED + SLEEPING + SUCCESS + WAITING +} +enum IncidentStatus { + IDENTIFIED + INVESTIGATING + MONITORING + RESOLVED +} +enum KeyType { + KEY_TYPE_ECDSA + KEY_TYPE_RSA_2048 + KEY_TYPE_RSA_4096 + KEY_TYPE_UNSPECIFIED + UNRECOGNIZED +} +enum MaintenanceStatus { + COMPLETED + INPROGRESS + NOTSTARTEDYET +} +"A thing that can be measured on Railway." +enum MetricMeasurement { + CPU_USAGE + DISK_USAGE_GB + EPHEMERAL_DISK_USAGE_GB + MEASUREMENT_UNSPECIFIED + MEMORY_USAGE_GB + NETWORK_RX_GB + NETWORK_TX_GB + UNRECOGNIZED +} +"A property that can be used to group metrics." +enum MetricTag { + DEPLOYMENT_ID + DEPLOYMENT_INSTANCE_ID + ENVIRONMENT_ID + KEY_UNSPECIFIED + PLUGIN_ID + PROJECT_ID + SERVICE_ID + UNRECOGNIZED + VOLUME_ID +} +enum PluginStatus { + DEPRECATED + LOCKED + REMOVED + RUNNING + STOPPED +} +enum PluginType { + mongodb + mysql + postgresql + redis +} +enum ProjectRole { + ADMIN + MEMBER + VIEWER +} +enum ReferralStatus { + REFEREE_CREDITED + REFERRER_CREDITED + REGISTERED +} +enum RegistrationStatus { + ONBOARDED + REGISTERED + WAITLISTED +} +enum ResourceOwnerType { + TEAM + USER +} +enum RestartPolicyType { + ALWAYS + NEVER + ON_FAILURE +} +enum SessionType { + BROWSER + CLI + FORUMS +} +enum SubscriptionPlanType { + hobby + pro + trial +} +enum SubscriptionState { + ACTIVE + CANCELLED + INACTIVE + PAST_DUE + UNPAID +} +enum TeamRole { + ADMIN + MEMBER +} +enum TemplateStatus { + HIDDEN + PUBLISHED + UNPUBLISHED +} +"Possible actions for a UsageAnomaly." +enum UsageAnomalyAction { + ALLOWED + AUTOBANNED + BANNED +} +"Possible flag reasons for a UsageAnomaly." +enum UsageAnomalyFlagReason { + HIGH_CPU_USAGE + HIGH_DISK_USAGE + HIGH_NETWORK_USAGE +} +enum UserFlag { + BETA +} +enum VolumeState { + DELETED + DELETING + ERROR + MIGRATING + MIGRATION_PENDING + READY + UPDATING +} +enum WorkflowStatus { + Complete + Error + NotFound + Running +} +input ApiTokenCreateInput { + name: String! + teamId: String +} +input BaseEnvironmentOverrideInput { + baseEnvironmentOverrideId: String +} +input CustomDomainCreateInput { + domain: String! + environmentId: String! + serviceId: String! +} +input DeploymentListInput { + environmentId: String + includeDeleted: Boolean + projectId: String + serviceId: String + status: DeploymentStatusInput +} +input DeploymentStatusInput { + in: [DeploymentStatus!] + notIn: [DeploymentStatus!] +} +input DeploymentTriggerCreateInput { + branch: String! + checkSuites: Boolean + environmentId: String! + projectId: String! + provider: String! + repository: String! + rootDirectory: String + serviceId: String! +} +input DeploymentTriggerUpdateInput { + branch: String + checkSuites: Boolean + repository: String + rootDirectory: String +} +input EnvironmentCreateInput { + ephemeral: Boolean + name: String! + projectId: String! + "When committing the changes immediately, skip any initial deployments." + skipInitialDeploys: Boolean + "Create the environment with all of the services, volumes, configuration, and variables from this source environment." + sourceEnvironmentId: String + "Stage the initial changes for the environment. If false (default), the changes will be committed immediately." + stageInitialChanges: Boolean +} +input EnvironmentTriggersDeployInput { + environmentId: String! + projectId: String! + serviceId: String! +} +input EventBatchTrackInput { + events: [EventTrackInput!]! +} +input EventTrackInput { + eventName: String! + properties: EventProperties + ts: String! +} +input ExplicitOwnerInput { + "The ID of the owner" + id: String! + "The type of owner" + type: ResourceOwnerType! +} +input FeatureFlagToggleInput { + flag: ActiveFeatureFlag! +} +input GitHubRepoDeployInput { + projectId: String! + repo: String! +} +input GitHubRepoUpdateInput { + environmentId: String! + projectId: String! + serviceId: String! +} +input HerokuImportVariablesInput { + environmentId: String! + herokuAppId: String! + projectId: String! + serviceId: String! +} +input IntegrationCreateInput { + config: JSON! + integrationAuthId: String + name: String! + projectId: String! +} +input IntegrationUpdateInput { + config: JSON! + integrationAuthId: String + name: String! + projectId: String! +} +input JobApplicationCreateInput { + email: String! + jobId: String! + name: String! + resume: Upload! + why: String! +} +input LoginSessionAuthInput { + code: String! + hostname: String +} +input MergeChange { + action: String! + name: String! + serviceId: String + type: String! + value: String! +} +input MissingCommandAlertInput { + page: String! + text: String! +} +input PluginCreateInput { + environmentId: String + friendlyName: String + name: String! + projectId: String! +} +input PluginRestartInput { + environmentId: String +} +input PluginUpdateInput { + friendlyName: String! +} +input PreferencesUpdateData { + buildFailedEmail: Boolean + changelogEmail: Boolean + communityEmail: Boolean + deployCrashedEmail: Boolean + marketingEmail: Boolean + token: String + usageEmail: Boolean +} +input PrivateNetworkCreateOrGetInput { + environmentId: String! + name: String! + projectId: String! + tags: [String!]! +} +input PrivateNetworkEndpointCreateOrGetInput { + environmentId: String! + privateNetworkId: String! + serviceId: String! + serviceName: String! + tags: [String!]! +} +input ProjectCreateInput { + defaultEnvironmentName: String + description: String + isPublic: Boolean + name: String + plugins: [String!] + prDeploys: Boolean + repo: ProjectCreateRepo + teamId: String +} +input ProjectCreateRepo { + branch: String! + fullRepoName: String! +} +input ProjectInviteUserInput { + email: String! + link: String! +} +input ProjectMemberRemoveInput { + projectId: String! + userId: String! +} +input ProjectMemberUpdateInput { + projectId: String! + role: ProjectRole! + userId: String! +} +input ProjectTokenCreateInput { + environmentId: String! + name: String! + projectId: String! +} +input ProjectTransferConfirmInput { + ownershipTransferId: String! + projectId: String! +} +input ProjectTransferInitiateInput { + memberId: String! + projectId: String! +} +input ProjectTransferToTeamInput { + teamId: String! +} +input ProjectUpdateInput { + baseEnvironmentId: String + description: String + isPublic: Boolean + name: String + prDeploys: Boolean + "[Experimental] Will be deprecated eventually" + prForks: Boolean +} +input RecoveryCodeValidateInput { + code: String! + twoFactorLinkingKey: String +} +input ReferralInfoUpdateInput { + code: String! +} +input ResetPluginCredentialsInput { + environmentId: String! +} +input ResetPluginInput { + environmentId: String! +} +input SendCommunityThreadNotificationEmailInput { + threadTitle: String! + threadUrl: String! + userIds: [String!]! +} +input ServiceConnectInput { + "The branch to connect to. e.g. 'main'" + branch: String + "Name of the Dockerhub or GHCR image to connect this service to." + image: String + "The full name of the repo to connect to. e.g. 'railwayapp/starters'" + repo: String +} +input ServiceCreateInput { + branch: String + "Environment ID. If the specified environment is a fork, the service will only be created in it. Otherwise it will created in all environments that are not forks of other environments" + environmentId: String + name: String + projectId: String! + source: ServiceSourceInput + variables: ServiceVariables +} +input ServiceDomainCreateInput { + environmentId: String! + serviceId: String! +} +input ServiceDomainUpdateInput { + domain: String! + environmentId: String! + serviceId: String! +} +input ServiceInstanceUpdateInput { + buildCommand: String + builder: Builder + cronSchedule: String + healthcheckPath: String + healthcheckTimeout: Int + nixpacksPlan: JSON + numReplicas: Int + railwayConfigFile: String + region: String + restartPolicyMaxRetries: Int + restartPolicyType: RestartPolicyType + rootDirectory: String + source: ServiceSourceInput + startCommand: String + watchPatterns: [String!] +} +input ServiceSourceInput { + image: String + repo: String +} +input ServiceUpdateInput { + icon: String + name: String +} +input SharedVariableConfigureInput { + disabledServiceIds: [String!]! + enabledServiceIds: [String!]! + environmentId: String! + name: String! + projectId: String! +} +input TCPProxyCreateInput { + applicationPort: Int! + environmentId: String! + serviceId: String! +} +input TeamBulkProjectTransferInput { + projectIds: [String!]! + teamId: String! +} +input TeamCreateAndSubscribeInput { + avatar: String + name: String! + paymentMethodId: String! +} +input TeamCreateInput { + avatar: String + name: String! +} +input TeamInviteCodeCreateInput { + role: String! +} +input TeamPermissionChangeInput { + role: TeamRole! + teamId: String! + userId: String! +} +input TeamUpdateInput { + avatar: String + name: String! +} +input TeamUserInviteInput { + code: String! + email: String! +} +input TeamUserRemoveInput { + userId: String! +} +input TelemetrySendInput { + command: String! + environmentId: String + error: String! + projectId: String + stacktrace: String! + version: String +} +input TemplateCloneInput { + code: String! + teamId: String +} +input TemplateCreateInput { + config: TemplateConfig! + demoProjectId: String + metadata: TemplateMetadata! + services: [TemplateServiceCreateInput!]! + teamId: String +} +input TemplateDeployInput { + environmentId: String + projectId: String + services: [TemplateDeployService!]! + teamId: String + templateCode: String +} +input TemplateDeployService { + commit: String + hasDomain: Boolean + healthcheckPath: String + id: String + isPrivate: Boolean + name: String + owner: String + rootDirectory: String + serviceIcon: String + serviceName: String! + startCommand: String + tcpProxyApplicationPort: Int + template: String! + variables: ServiceVariables + volumes: [TemplateVolume!] +} +input TemplateGenerateInput { + projectId: String! +} +input TemplatePublishInput { + category: String! + description: String! + image: String + readme: String! + teamId: String +} +input TemplateServiceCreateInput { + config: TemplateServiceConfig! +} +input TemplateServiceUpdateInput { + config: TemplateServiceConfig! + id: String +} +input TemplateUpdateInput { + config: TemplateConfig! + demoProjectId: String + "An admin-only flag to force-update a template." + forceUpdate: Boolean = false + metadata: TemplateMetadata! + services: [TemplateServiceUpdateInput!]! + teamId: String +} +input TwoFactorInfoCreateInput { + token: String! +} +input TwoFactorInfoValidateInput { + token: String! + twoFactorLinkingKey: String +} +input UsageLimitRemoveInput { + customerId: String! +} +input UsageLimitSetInput { + customerId: String! + hardLimitDollars: Int + softLimitDollars: Int! +} +input UserFlagsRemoveInput { + flags: [UserFlag!]! + userId: String +} +input UserFlagsSetInput { + flags: [UserFlag!]! + userId: String +} +input UserProfileUpdateInput { + bio: String + isPublic: Boolean! + website: String +} +input UserUpdateInput { + avatar: String + name: String + username: String +} +input VariableCollectionUpsertInput { + environmentId: String! + projectId: String! + "When set to true, removes all existing variables before upserting the new collection." + replace: Boolean = false + serviceId: String + variables: ServiceVariables! +} +input VariableDeleteInput { + environmentId: String! + name: String! + projectId: String! + serviceId: String +} +input VariableUpsertInput { + environmentId: String! + name: String! + projectId: String! + serviceId: String + value: String! +} +input VolumeCreateInput { + "The environment to deploy the volume instances into. If `null`, the volume will not be deployed to any environment. `undefined` will deploy to all environments." + environmentId: String + "The path in the container to mount the volume to" + mountPath: String! + "The project to create the volume in" + projectId: String! + "The service to attach the volume to. If not provided, the volume will be disconnected." + serviceId: String +} +input VolumeInstanceUpdateInput { + "The mount path of the volume instance. If not provided, the mount path will not be updated." + mountPath: String + "The service to attach the volume to. If not provided, the volume will be disconnected." + serviceId: String + "The state of the volume instance. If not provided, the state will not be updated." + state: VolumeState +} +input VolumeUpdateInput { + "The name of the volume" + name: String +} +input WebhookCreateInput { + projectId: String! + url: String! +} +input WebhookUpdateInput { + url: String! +} +"Exposes a URL that specifies the behavior of this scalar." +directive @specifiedBy( + "The URL that specifies the behavior of this scalar." + url: String! +) on SCALAR From 8f80337be7af03ba09a8df44bb8bb670b67f470c Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:07:09 +0000 Subject: [PATCH 02/20] remove old queries --- src/gql/mutations/strings/PluginCreate.graphql | 5 ----- src/gql/mutations/strings/PluginDelete.graphql | 3 --- 2 files changed, 8 deletions(-) delete mode 100644 src/gql/mutations/strings/PluginCreate.graphql delete mode 100644 src/gql/mutations/strings/PluginDelete.graphql diff --git a/src/gql/mutations/strings/PluginCreate.graphql b/src/gql/mutations/strings/PluginCreate.graphql deleted file mode 100644 index ab363b553..000000000 --- a/src/gql/mutations/strings/PluginCreate.graphql +++ /dev/null @@ -1,5 +0,0 @@ -mutation PluginCreate($name: String!, $projectId: String!) { - pluginCreate(input: { name: $name, projectId: $projectId }) { - id - } -} diff --git a/src/gql/mutations/strings/PluginDelete.graphql b/src/gql/mutations/strings/PluginDelete.graphql deleted file mode 100644 index 6ab8877a7..000000000 --- a/src/gql/mutations/strings/PluginDelete.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation PluginDelete($id: String!) { - pluginDelete(id: $id) -} From 408179c20ca8bf7798845f1a7ff8c524feac32cb Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:07:54 +0000 Subject: [PATCH 03/20] remove delete command --- src/commands/delete.rs | 91 ------------------------------------------ src/commands/mod.rs | 1 - src/main.rs | 1 - 3 files changed, 93 deletions(-) delete mode 100644 src/commands/delete.rs diff --git a/src/commands/delete.rs b/src/commands/delete.rs deleted file mode 100644 index 20121d2e4..000000000 --- a/src/commands/delete.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::time::Duration; - -use anyhow::bail; -use is_terminal::IsTerminal; - -use crate::{ - consts::TICK_STRING, - controllers::project::get_project, - errors::RailwayError, - interact_or, - util::prompt::{prompt_confirm, prompt_multi_options, prompt_text}, -}; - -use super::*; - -/// Delete plugins from a project -#[derive(Parser)] -pub struct Args {} - -pub async fn command(_args: Args, _json: bool) -> Result<()> { - interact_or!("Cannot delete plugins in non-interactive mode"); - - let configs = Configs::new()?; - - let client = GQLClient::new_authorized(&configs)?; - let linked_project = configs.get_linked_project().await?; - - let is_two_factor_enabled = { - let vars = queries::two_factor_info::Variables {}; - - let info = - post_graphql::(&client, configs.get_backboard(), vars) - .await? - .two_factor_info; - - info.is_verified - }; - - if is_two_factor_enabled { - let token = prompt_text("Enter your 2FA code")?; - let vars = mutations::validate_two_factor::Variables { token }; - - let valid = - post_graphql::(&client, configs.get_backboard(), vars) - .await? - .two_factor_info_validate; - - if !valid { - return Err(RailwayError::InvalidTwoFactorCode.into()); - } - } - - let project = get_project(&client, &configs, linked_project.project).await?; - - let nodes = project.plugins.edges; - let project_plugins: Vec<_> = nodes.iter().map(|p| p.node.name.to_string()).collect(); - let selected = prompt_multi_options("Select plugins to delete", project_plugins)?; - - for plugin in selected { - let id = nodes - .iter() - .find(|p| p.node.name.to_string() == plugin) - .ok_or_else(|| RailwayError::PluginNotFound(plugin.clone()))? - .node - .id - .clone(); - - let vars = mutations::plugin_delete::Variables { id }; - - let confirmed = - prompt_confirm(format!("Are you sure you want to delete {plugin}?").as_str())?; - - if !confirmed { - return Ok(()); - } - - let spinner = indicatif::ProgressBar::new_spinner() - .with_style( - indicatif::ProgressStyle::default_spinner() - .tick_chars(TICK_STRING) - .template("{spinner:.green} {msg}")?, - ) - .with_message(format!("Deleting {plugin}...")); - spinner.enable_steady_tick(Duration::from_millis(100)); - - post_graphql::(&client, configs.get_backboard(), vars).await?; - - spinner.finish_with_message(format!("Deleted {plugin}")); - } - Ok(()) -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1ef380602..f9f4a610c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,7 +6,6 @@ pub(super) use colored::Colorize; pub mod add; pub mod completion; pub mod connect; -pub mod delete; pub mod docs; pub mod domain; pub mod down; diff --git a/src/main.rs b/src/main.rs index ef8ecf4aa..592d2c71c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,6 @@ commands_enum!( add, completion, connect, - delete, domain, docs, down, From bbc4611bb570265fac95bd01ced38ed9827ebd95 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:08:44 +0000 Subject: [PATCH 04/20] remove PLUGINS const --- src/consts.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index d9fadc347..379ac9b66 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -3,7 +3,4 @@ pub const fn get_user_agent() -> &'static str { } pub const TICK_STRING: &str = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ "; - -pub const PLUGINS: &[&str] = &["PostgreSQL", "MySQL", "Redis", "MongoDB"]; - pub const NON_INTERACTIVE_FAILURE: &str = "This command is only available in interactive mode"; From 19836b72d019f1cb1b87bad27bc24d80b659bcf5 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:08:54 +0000 Subject: [PATCH 05/20] change errors --- src/errors.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index eceba63ed..31d7df8f5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -35,19 +35,13 @@ pub enum RailwayError { )] EnvironmentNotFound(String), - #[error("Plugin \"{0}\" not found.")] - PluginNotFound(String), - #[error("Service \"{0}\" not found.\nRun `railway service` to connect to a service.")] ServiceNotFound(String), - #[error("Service or plugin \"{0}\" not found.")] - ServiceOrPluginNotFound(String), - - #[error("Project has no services or plugins.")] - ProjectHasNoServicesOrPlugins, + #[error("Project has no services.")] + ProjectHasNoServices, - #[error("No service linked and no plugins found\nRun `railway service` to link a service")] + #[error("No service linked\nRun `railway service` to link a service")] NoServiceLinked, #[error("2FA code is incorrect. Please try again.")] From 22bb05d305563e06eeaf7436b5fb1cc47a0132ff Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:09:07 +0000 Subject: [PATCH 06/20] dont query for plugins anymore --- src/gql/queries/strings/Project.graphql | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/gql/queries/strings/Project.graphql b/src/gql/queries/strings/Project.graphql index 8191c6dd8..c495f4dcb 100644 --- a/src/gql/queries/strings/Project.graphql +++ b/src/gql/queries/strings/Project.graphql @@ -2,15 +2,6 @@ query Project($id: String!) { project(id: $id) { id name - plugins { - edges { - node { - id - name - friendlyName - } - } - } environments { edges { node { From 27e0bc4c90f3322c73c165fc5f90f886105c7b18 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:11:42 +0000 Subject: [PATCH 07/20] new database controller, remove plugin controller --- src/controllers/database.rs | 10 ++++++++++ src/controllers/mod.rs | 2 +- src/controllers/plugin.rs | 13 ------------- 3 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 src/controllers/database.rs delete mode 100644 src/controllers/plugin.rs diff --git a/src/controllers/database.rs b/src/controllers/database.rs new file mode 100644 index 000000000..8ed98f682 --- /dev/null +++ b/src/controllers/database.rs @@ -0,0 +1,10 @@ +use clap::ValueEnum; +use strum::{Display, EnumIter}; + +#[derive(Debug, Clone, ValueEnum, EnumIter, Display)] +pub enum DatabaseType { + PostgreSQL, + MySQL, + Redis, + MongoDB, +} diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 7458b6813..af91d98a7 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1,6 +1,6 @@ +pub mod database; pub mod deployment; pub mod environment; -pub mod plugin; pub mod project; pub mod user; pub mod variables; diff --git a/src/controllers/plugin.rs b/src/controllers/plugin.rs deleted file mode 100644 index 0b5ea9621..000000000 --- a/src/controllers/plugin.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::commands::queries::project::PluginType; - -impl ToString for PluginType { - fn to_string(&self) -> String { - match self { - PluginType::postgresql => "PostgreSQL".to_owned(), - PluginType::mysql => "MySQL".to_owned(), - PluginType::redis => "Redis".to_owned(), - PluginType::mongodb => "MongoDB".to_owned(), - PluginType::Other(other) => other.to_owned(), - } - } -} From 06538886c125e7dfe8994c3772d8d84c909f1bca Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:11:56 +0000 Subject: [PATCH 08/20] properly remove old mutations --- src/gql/mutations/mod.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/gql/mutations/mod.rs b/src/gql/mutations/mod.rs index 36d16cd67..a36d26fe7 100644 --- a/src/gql/mutations/mod.rs +++ b/src/gql/mutations/mod.rs @@ -24,22 +24,6 @@ pub struct LoginSessionConsume; )] pub struct LoginSessionCreate; -#[derive(GraphQLQuery)] -#[graphql( - schema_path = "src/gql/schema.graphql", - query_path = "src/gql/mutations/strings/PluginCreate.graphql", - response_derives = "Debug, Serialize, Clone" -)] -pub struct PluginCreate; - -#[derive(GraphQLQuery)] -#[graphql( - schema_path = "src/gql/schema.graphql", - query_path = "src/gql/mutations/strings/PluginDelete.graphql", - response_derives = "Debug, Serialize, Clone" -)] -pub struct PluginDelete; - #[derive(GraphQLQuery)] #[graphql( schema_path = "src/gql/schema.graphql", From b22896fa700202b4acef2a66c88072e9fef63735 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:12:13 +0000 Subject: [PATCH 09/20] update prompts --- src/util/prompt.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/util/prompt.rs b/src/util/prompt.rs index 9ccd49988..12f4be3c9 100644 --- a/src/util/prompt.rs +++ b/src/util/prompt.rs @@ -1,9 +1,6 @@ use std::fmt::Display; -use crate::commands::{ - queries::project::{ProjectProjectPluginsEdgesNode, ProjectProjectServicesEdgesNode}, - Configs, -}; +use crate::commands::{queries::project::ProjectProjectServicesEdgesNode, Configs}; use anyhow::{Context, Result}; pub fn prompt_options(message: &str, options: Vec) -> Result { @@ -73,12 +70,3 @@ impl<'a> Display for PromptService<'a> { write!(f, "{}", self.0.name) } } - -#[derive(Debug, Clone)] -pub struct PromptPlugin<'a>(pub &'a ProjectProjectPluginsEdgesNode); - -impl<'a> Display for PromptPlugin<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.friendly_name) - } -} From 73e130835e6deb5efe0c93b5e15e48417e18c454 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:12:27 +0000 Subject: [PATCH 10/20] Refactor variable controllers --- src/controllers/variables.rs | 84 ------------------------------------ 1 file changed, 84 deletions(-) diff --git a/src/controllers/variables.rs b/src/controllers/variables.rs index fa7a24d98..a82b85274 100644 --- a/src/controllers/variables.rs +++ b/src/controllers/variables.rs @@ -6,8 +6,6 @@ use anyhow::Result; use reqwest::Client; use std::collections::BTreeMap; -use super::project::PluginOrService; - pub async fn get_service_variables( client: &Client, configs: &Configs, @@ -30,85 +28,3 @@ pub async fn get_service_variables( Ok(variables) } - -// note - this is only for projects with no services -pub async fn get_all_plugin_variables( - client: &Client, - configs: &Configs, - project_id: String, - environment_id: String, - plugins: &[String], -) -> Result> { - let mut plugin_variables = BTreeMap::new(); - for plugin in plugins { - let mut vars = get_plugin_variables( - client, - configs, - project_id.clone(), - environment_id.clone(), - plugin.clone(), - ) - .await?; - plugin_variables.append(&mut vars); - } - Ok(plugin_variables) -} - -pub async fn get_plugin_variables( - client: &Client, - configs: &Configs, - project_id: String, - environment_id: String, - plugin_id: String, -) -> Result> { - let vars = queries::variables_for_plugin::Variables { - project_id: project_id.clone(), - environment_id: environment_id.clone(), - plugin_id: plugin_id.clone(), - }; - let variables = - post_graphql::(client, configs.get_backboard(), vars) - .await? - .variables; - - Ok(variables) -} - -pub async fn get_plugin_or_service_variables( - client: &Client, - configs: &Configs, - project_id: String, - environment_id: String, - plugin_or_service: &PluginOrService, -) -> Result> { - let variables = match plugin_or_service { - PluginOrService::Plugin(plugin) => { - let query = queries::variables_for_plugin::Variables { - project_id: project_id.clone(), - environment_id: environment_id.clone(), - plugin_id: plugin.id.clone(), - }; - - post_graphql::(client, configs.get_backboard(), query) - .await? - .variables - } - PluginOrService::Service(service) => { - let query = queries::variables_for_service_deployment::Variables { - project_id: project_id.clone(), - environment_id: environment_id.clone(), - service_id: service.id.clone(), - }; - - post_graphql::( - client, - configs.get_backboard(), - query, - ) - .await? - .variables_for_service_deployment - } - }; - - Ok(variables) -} From 8c8c0d14b5e2c6c506c6bec154529257dd3b8c6f Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:12:43 +0000 Subject: [PATCH 11/20] Refactor connect command to remove usage of plugins everywhere --- src/commands/connect.rs | 148 +++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 85 deletions(-) diff --git a/src/commands/connect.rs b/src/commands/connect.rs index 9e9ee092e..246ddc469 100644 --- a/src/commands/connect.rs +++ b/src/commands/connect.rs @@ -3,16 +3,15 @@ use std::{collections::BTreeMap, fmt::Display}; use tokio::process::Command; use which::which; -use crate::controllers::project::get_plugin_or_service; use crate::controllers::{ - environment::get_matched_environment, - project::{get_project, PluginOrService}, - variables::get_plugin_or_service_variables, + database::DatabaseType, environment::get_matched_environment, project::get_project, + variables::get_service_variables, }; use crate::errors::RailwayError; use crate::util::prompt::prompt_select; +use crate::{controllers::project::get_service, queries::project::ProjectProjectServicesEdgesNode}; -use super::{queries::project::PluginType, *}; +use super::*; /// Connect to a plugin's shell (psql for Postgres, mongosh for MongoDB, etc.) #[derive(Parser)] @@ -25,12 +24,9 @@ pub struct Args { environment: Option, } -impl Display for PluginOrService { +impl Display for ProjectProjectServicesEdgesNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - PluginOrService::Plugin(plugin) => write!(f, "{} (legacy)", plugin.friendly_name), - PluginOrService::Service(service) => write!(f, "{}", service.name), - } + write!(f, "{}", self.name) } } @@ -45,21 +41,20 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { let project = get_project(&client, &configs, linked_project.project.clone()).await?; - let plugin_or_service = args + let service = args .service_name .clone() - .map(|name| get_plugin_or_service(&project, name)) + .map(|name| get_service(&project, name)) .unwrap_or_else(|| { - let mut nodes_to_prompt: Vec = Vec::new(); - for plugin in &project.plugins.edges { - nodes_to_prompt.push(PluginOrService::Plugin(plugin.node.clone())); - } - for service in &project.services.edges { - nodes_to_prompt.push(PluginOrService::Service(service.node.clone())); - } + let nodes_to_prompt = project + .services + .edges + .iter() + .map(|s| s.node.clone()) + .collect::>(); if nodes_to_prompt.is_empty() { - return Err(RailwayError::ProjectHasNoServicesOrPlugins.into()); + return Err(RailwayError::ProjectHasNoServices.into()); } prompt_select("Select service", nodes_to_prompt).context("No service selected") @@ -67,98 +62,82 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { let environment_id = get_matched_environment(&project, environment)?.id; - let variables = get_plugin_or_service_variables( + let variables = get_service_variables( &client, &configs, linked_project.project, environment_id.clone(), - &plugin_or_service, + service.name, ) .await?; + let database_type = { + let service_instance = service + .service_instances + .edges + .iter() + .find(|si| si.node.environment_id == environment_id); + + service_instance + .and_then(|si| si.node.source.clone()) + .and_then(|source| source.image) + .map(|image: String| image.to_lowercase()) + .and_then(|image: String| { + if image.contains("postgres") + || image.contains("postgis") + || image.contains("timescale") + { + Some(DatabaseType::PostgreSQL) + } else if image.contains("redis") { + Some(DatabaseType::Redis) + } else if image.contains("mongo") { + Some(DatabaseType::MongoDB) + } else if image.contains("mysql") { + Some(DatabaseType::MySQL) + } else { + None + } + }) + }; + if let Some(db_type) = database_type { + let (cmd_name, args) = get_connect_command(db_type, variables)?; - let plugin_type = plugin_or_service - .get_plugin_type(environment_id) - .ok_or_else(|| RailwayError::UnknownDatabaseType(plugin_or_service.get_name()))?; - - let (cmd_name, args) = get_connect_command(plugin_type, variables)?; - - if which(cmd_name.clone()).is_err() { - bail!("{} must be installed to continue", cmd_name); - } - - Command::new(cmd_name.as_str()) - .args(args) - .spawn()? - .wait() - .await?; - - Ok(()) -} - -impl PluginOrService { - pub fn get_name(&self) -> String { - match self { - PluginOrService::Plugin(plugin) => plugin.friendly_name.clone(), - PluginOrService::Service(service) => service.name.clone(), + if which(cmd_name.clone()).is_err() { + bail!("{} must be installed to continue", cmd_name); } - } - pub fn get_plugin_type(&self, environment_id: String) -> Option { - match self { - PluginOrService::Plugin(plugin) => Some(plugin.name.clone()), - PluginOrService::Service(service) => { - let service_instance = service - .service_instances - .edges - .iter() - .find(|si| si.node.environment_id == environment_id); - - service_instance - .and_then(|si| si.node.source.clone()) - .and_then(|source| source.image) - .map(|image: String| image.to_lowercase()) - .and_then(|image: String| { - if image.contains("postgres") - || image.contains("postgis") - || image.contains("timescale") - { - Some(PluginType::postgresql) - } else if image.contains("redis") { - Some(PluginType::redis) - } else if image.contains("mongo") { - Some(PluginType::mongodb) - } else if image.contains("mysql") { - Some(PluginType::mysql) - } else { - None - } - }) - } - } + Command::new(cmd_name.as_str()) + .args(args) + .spawn()? + .wait() + .await?; + + Ok(()) + } else { + bail!("No database found for service") } } fn get_connect_command( - plugin_type: PluginType, + plugin_type: DatabaseType, variables: BTreeMap, ) -> Result<(String, Vec)> { let pass_arg; // Hack to get ownership of formatted string outside match let default = &"".to_string(); let (cmd_name, args): (&str, Vec<&str>) = match &plugin_type { - PluginType::postgresql => ( + DatabaseType::PostgreSQL => ( "psql", vec![variables.get("DATABASE_URL").unwrap_or(default)], ), - PluginType::redis => ( + DatabaseType::Redis => ( "redis-cli", vec!["-u", variables.get("REDIS_URL").unwrap_or(default)], ), - PluginType::mongodb => ( + DatabaseType::MongoDB => ( "mongosh", vec![variables.get("MONGO_URL").unwrap_or(default).as_str()], ), - PluginType::mysql => { + DatabaseType::MySQL => { // -p is a special case as it requires no whitespace between arg and value pass_arg = format!("-p{}", variables.get("MYSQLPASSWORD").unwrap_or(default)); ( @@ -176,7 +155,6 @@ fn get_connect_command( ], ) } - PluginType::Other(o) => bail!("Unsupported plugin type {}", o), }; Ok(( From 20236e6e3b86aad883b3198c76cf6c7e98275e5f Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:12:52 +0000 Subject: [PATCH 12/20] add strum lol --- Cargo.lock | 29 +++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 30 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 57f5e9871..dedb0c00c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,6 +1546,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", "synchronized-writer", "tar", "textwrap", @@ -1725,6 +1726,12 @@ dependencies = [ "base64 0.21.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" @@ -1964,6 +1971,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.15", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index adafbcc39..197a8428f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,3 +69,4 @@ ctrlc = "3.2.5" which = "4.4.0" thiserror = "1.0.40" winapi = {version="0.3.9", features = ["minwindef", "tlhelp32", "processthreadsapi", "handleapi", "winerror"]} +strum = { version = "0.26.1", features = ["derive"] } From b9e7324791e92a0f14f4654a1c39084f775a1beb Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:13:42 +0000 Subject: [PATCH 13/20] other changes, doesnt work yet --- src/commands/add.rs | 65 ++++--------------- src/commands/run.rs | 65 +++++-------------- src/commands/status.rs | 9 +-- src/commands/variables.rs | 26 +------- src/controllers/project.rs | 29 ++------- .../mutations/strings/TemplateDeploy.graphql | 3 + src/gql/queries/mod.rs | 8 +++ .../queries/strings/TemplateDetail.graphql | 12 ++++ 8 files changed, 62 insertions(+), 155 deletions(-) create mode 100644 src/gql/mutations/strings/TemplateDeploy.graphql create mode 100644 src/gql/queries/strings/TemplateDetail.graphql diff --git a/src/commands/add.rs b/src/commands/add.rs index 72665b7f4..d24a3d128 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -1,23 +1,22 @@ -use std::time::Duration; - use anyhow::bail; -use clap::ValueEnum; use is_terminal::IsTerminal; +use std::time::Duration; +use strum::IntoEnumIterator; use crate::{ - consts::{PLUGINS, TICK_STRING}, - controllers::project::get_project, + consts::TICK_STRING, + controllers::{database::DatabaseType, project::get_project}, util::prompt::prompt_multi_options, }; -use super::{queries::project::PluginType, *}; +use super::*; /// Add a new plugin to your project #[derive(Parser)] pub struct Args { - /// The name of the plugin to add + /// The name of the database to add #[arg(short, long, value_enum)] - plugin: Vec, + database: Vec, } pub async fn command(args: Args, _json: bool) -> Result<()> { @@ -28,38 +27,13 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { let project = get_project(&client, &configs, linked_project.project.clone()).await?; - let project_plugins: Vec<_> = project - .plugins - .edges - .iter() - .map(|p| p.node.name.to_string()) - .collect(); - - let filtered_plugins: Vec<_> = PLUGINS - .iter() - .map(|p| p.to_string()) - .filter(|plugin| !project_plugins.contains(&plugin.to_string())) - .collect(); - - let selected = if !std::io::stdout().is_terminal() || !args.plugin.is_empty() { - if args.plugin.is_empty() { + let databases = if args.database.is_empty() { + if !std::io::stdout().is_terminal() { bail!("No plugins specified"); } - let filtered: Vec<_> = args - .plugin - .iter() - .map(clap_plugin_enum_to_plugin_enum) - .map(|p| p.to_string()) - .filter(|plugin| !project_plugins.contains(&plugin.to_string())) - .collect(); - - if filtered.is_empty() { - bail!("Plugins already exist"); - } - - filtered + prompt_multi_options("Select databases to add", DatabaseType::iter().collect())? } else { - prompt_multi_options("Select plugins to add", filtered_plugins)? + args.database }; if selected.is_empty() { @@ -92,20 +66,3 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { Ok(()) } - -#[derive(ValueEnum, Clone, Debug)] -enum ClapPluginEnum { - Postgresql, - Mysql, - Redis, - Mongodb, -} - -fn clap_plugin_enum_to_plugin_enum(clap_plugin_enum: &ClapPluginEnum) -> PluginType { - match clap_plugin_enum { - ClapPluginEnum::Postgresql => PluginType::postgresql, - ClapPluginEnum::Mysql => PluginType::mysql, - ClapPluginEnum::Redis => PluginType::redis, - ClapPluginEnum::Mongodb => PluginType::mongodb, - } -} diff --git a/src/commands/run.rs b/src/commands/run.rs index 55ce6d215..82cee090f 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -3,9 +3,8 @@ use is_terminal::IsTerminal; use crate::{ controllers::{ - environment::get_matched_environment, - project::get_project, - variables::{get_all_plugin_variables, get_service_variables}, + environment::get_matched_environment, project::get_project, + variables::get_service_variables, }, errors::RailwayError, util::prompt::{prompt_select, PromptService}, @@ -29,16 +28,11 @@ pub struct Args { args: Vec, } -enum ServiceOrPlugins { - Service(String), - Plugins(Vec), -} - -async fn get_service_or_plugins( +async fn get_service( configs: &Configs, project: &ProjectProject, service_arg: Option, -) -> Result { +) -> Result { let linked_project = configs.get_linked_project().await?; let services = project.services.edges.iter().collect::>(); @@ -49,26 +43,18 @@ async fn get_service_or_plugins( .iter() .find(|service| service.node.name == service_arg || service.node.id == service_arg); if let Some(service_id) = service_id { - ServiceOrPlugins::Service(service_id.node.id.to_owned()) + service_id.node.id.to_owned() } else { bail!("Service not found"); } } else if let Some(service) = linked_project.service { // If the user didn't specify a service, but we have a linked service, use that - ServiceOrPlugins::Service(service) + service } else { // If the user didn't specify a service, and we don't have a linked service, get the first service if services.is_empty() { - // If there are no services, backboard will generate one for us - ServiceOrPlugins::Plugins( - project - .plugins - .edges - .iter() - .map(|plugin| plugin.node.id.to_owned()) - .collect(), - ) + bail!(RailwayError::ProjectHasNoServices) } else { // If there are multiple services, prompt the user to select one if std::io::stdout().is_terminal() { @@ -76,7 +62,7 @@ async fn get_service_or_plugins( services.iter().map(|s| PromptService(&s.node)).collect(); let service = prompt_select("Select a service to pull variables from", prompt_services)?; - ServiceOrPlugins::Service(service.0.id.clone()) + service.0.id.clone() } else { bail!("Multiple services found. Please specify a service to pull variables from.") } @@ -98,31 +84,16 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { .unwrap_or(linked_project.environment.clone()); let environment_id = get_matched_environment(&project, environment)?.id; - let service = get_service_or_plugins(&configs, &project, args.service).await?; - - let variables = match service { - ServiceOrPlugins::Service(service_id) => { - get_service_variables( - &client, - &configs, - linked_project.project.clone(), - environment_id, - service_id, - ) - .await? - } - ServiceOrPlugins::Plugins(plugin_ids) => { - // we fetch all the plugin variables - get_all_plugin_variables( - &client, - &configs, - linked_project.project.clone(), - environment_id, - &plugin_ids, - ) - .await? - } - }; + let service = get_service(&configs, &project, args.service).await?; + + let variables = get_service_variables( + &client, + &configs, + linked_project.project.clone(), + environment_id, + service, + ) + .await?; // a bit janky :/ ctrlc::set_handler(move || { diff --git a/src/commands/status.rs b/src/commands/status.rs index 966d0127c..446738a6f 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -4,7 +4,7 @@ use super::*; /// Show information about the current project #[derive(Parser)] -pub struct Args {} +pub struct Args; pub async fn command(_args: Args, json: bool) -> Result<()> { let configs = Configs::new()?; @@ -27,12 +27,7 @@ pub async fn command(_args: Args, json: bool) -> Result<()> { .blue() .bold() ); - if !project.plugins.edges.is_empty() { - println!("Plugins:"); - for plugin in project.plugins.edges.iter().map(|plugin| &plugin.node) { - println!("{}", format!("{:?}", plugin.name).dimmed().bold()); - } - } + if let Some(linked_service) = linked_project.service { let service = project .services diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 96f3d811f..dd23926ed 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -1,12 +1,6 @@ -use std::fmt::Display; - +use super::*; use crate::{controllers::project::get_project, errors::RailwayError, table::Table}; -use super::{ - queries::project::{PluginType, ProjectProjectPluginsEdgesNode}, - *, -}; - /// Show variables for active environment #[derive(Parser)] pub struct Args { @@ -94,21 +88,3 @@ pub async fn command(args: Args, json: bool) -> Result<()> { Ok(()) } - -struct Plugin<'a>(&'a ProjectProjectPluginsEdgesNode); - -impl<'a> Display for Plugin<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match &self.0.name { - PluginType::mongodb => "MongoDB", - PluginType::mysql => "MySQL", - PluginType::postgresql => "PostgreSQL", - PluginType::redis => "Redis", - PluginType::Other(plugin) => plugin, - } - ) - } -} diff --git a/src/controllers/project.rs b/src/controllers/project.rs index 36f6da3f1..fda6b42be 100644 --- a/src/controllers/project.rs +++ b/src/controllers/project.rs @@ -5,9 +5,7 @@ use crate::{ commands::{ queries::{ self, - project::{ - ProjectProject, ProjectProjectPluginsEdgesNode, ProjectProjectServicesEdgesNode, - }, + project::{ProjectProject, ProjectProjectServicesEdgesNode}, }, Configs, }, @@ -15,11 +13,6 @@ use crate::{ }; use anyhow::{bail, Result}; -pub enum PluginOrService { - Plugin(ProjectProjectPluginsEdgesNode), - Service(ProjectProjectServicesEdgesNode), -} - pub async fn get_project( client: &Client, configs: &Configs, @@ -43,27 +36,19 @@ pub async fn get_project( Ok(project) } -pub fn get_plugin_or_service( +pub fn get_service( project: &ProjectProject, - service_or_plugin_name: String, -) -> Result { + service_name: String, +) -> Result { let service = project .services .edges .iter() - .find(|edge| edge.node.name.to_lowercase() == service_or_plugin_name.to_lowercase()); - - let plugin = project.plugins.edges.iter().find(|edge| { - edge.node.friendly_name.to_lowercase() == service_or_plugin_name.to_lowercase() - }); + .find(|edge| edge.node.name.to_lowercase() == service_name.to_lowercase()); if let Some(service) = service { - return Ok(PluginOrService::Service(service.node.clone())); - } else if let Some(plugin) = plugin { - return Ok(PluginOrService::Plugin(plugin.node.clone())); + return Ok(service.node.clone()); } - bail!(RailwayError::ServiceOrPluginNotFound( - service_or_plugin_name - )) + bail!(RailwayError::ServiceNotFound(service_name)) } diff --git a/src/gql/mutations/strings/TemplateDeploy.graphql b/src/gql/mutations/strings/TemplateDeploy.graphql new file mode 100644 index 000000000..5609e6839 --- /dev/null +++ b/src/gql/mutations/strings/TemplateDeploy.graphql @@ -0,0 +1,3 @@ +mutation DatabaseDeploy($code: String!, $projectId: String!) { + templateDeploy(input) +} \ No newline at end of file diff --git a/src/gql/queries/mod.rs b/src/gql/queries/mod.rs index b0e41b38f..c0f7d15d0 100644 --- a/src/gql/queries/mod.rs +++ b/src/gql/queries/mod.rs @@ -101,3 +101,11 @@ pub struct Domains; response_derives = "Debug, Serialize, Clone" )] pub struct ProjectToken; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/gql/schema.graphql", + query_path = "src/gql/queries/strings/TemplateDetail.graphql", + response_derives = "Debug, Serialize, Clone" +)] +pub struct TemplateDetail; diff --git a/src/gql/queries/strings/TemplateDetail.graphql b/src/gql/queries/strings/TemplateDetail.graphql new file mode 100644 index 000000000..15e1c305c --- /dev/null +++ b/src/gql/queries/strings/TemplateDetail.graphql @@ -0,0 +1,12 @@ +query TemplateDetail($code: String!) { + template(code: $code) { + services { + edges { + node { + id + config + } + } + } + } +} \ No newline at end of file From 70bb57f68065ed3062d917c4096d376559bc97b7 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:17:21 +0000 Subject: [PATCH 14/20] why doesnt this workkkkk --- Cargo.lock | 12 +- src/commands/add.rs | 172 ++++++++++++++++-- src/controllers/database.rs | 11 ++ src/gql/mutations/mod.rs | 18 ++ .../mutations/strings/TemplateDeploy.graphql | 9 +- src/gql/queries/mod.rs | 54 ++++++ 6 files changed, 248 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e947b565f..82961378a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1784,12 +1784,6 @@ dependencies = [ "base64 0.21.0", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "rustls-pemfile" version = "2.0.0" @@ -1817,6 +1811,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" diff --git a/src/commands/add.rs b/src/commands/add.rs index d24a3d128..86abd423a 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -1,11 +1,13 @@ use anyhow::bail; use is_terminal::IsTerminal; +use std::collections::BTreeMap; use std::time::Duration; use strum::IntoEnumIterator; use crate::{ consts::TICK_STRING, controllers::{database::DatabaseType, project::get_project}, + mutations::TemplateVolume, util::prompt::prompt_multi_options, }; @@ -36,33 +38,163 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { args.database }; - if selected.is_empty() { + if databases.is_empty() { bail!("No plugins selected"); } - for plugin in selected { - let vars = mutations::plugin_create::Variables { + for db in databases { + // fetch template detail + let details = post_graphql::( + &client, + configs.get_backboard(), + queries::template_detail::Variables { code: db.to_slug() }, + ) + .await?; + let services: Vec = details + .template + .services + .edges + .iter() + .map(|s| { + let mut s_var = BTreeMap::::new(); + let mut s_vol = Vec::::new(); + // all variables in a db template have default values, this is safe + s.node.config.variables.iter().for_each(|v| { + s_var.insert(v.name.clone(), v.default_value.clone().unwrap()); + }); + if let Some(volumes) = s.node.config.volumes.clone() { + volumes.iter().for_each(|v| { + s_vol.push(TemplateVolume { + mount_path: v.mount_path.clone(), + name: v.name.clone(), + }) + }) + } + impl std::fmt::Debug for mutations::template_deploy::TemplateDeployService { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TemplateDeployService") + .field("commit", &self.commit) + .field("has_domain", &self.has_domain) + .field("healthcheck_path", &self.healthcheck_path) + .field("id", &self.id) + .field("is_private", &self.is_private) + .field("name", &self.name) + .field("owner", &self.owner) + .field("root_directory", &self.root_directory) + .field("service_icon", &self.service_icon) + .field("service_name", &self.service_name) + .field("start_command", &self.start_command) + .field( + "tcp_proxy_application_port", + &self.tcp_proxy_application_port, + ) + .field("template", &self.template) + .field("variables", &self.variables) + .field("volumes", &self.volumes) + .finish() + } + } + mutations::template_deploy::TemplateDeployService { + commit: None, + has_domain: s + .node + .config + .domains + .clone() + .iter() + .find(|d| d.has_service_domain) + .map(|s| s.has_service_domain), + healthcheck_path: None, + id: Some(s.node.id.clone()), + is_private: None, + name: Some(s.node.config.name.clone()), + owner: None, + root_directory: None, + service_icon: s.node.config.icon.clone(), + service_name: s.node.config.name.clone(), + start_command: s + .node + .config + .deploy_config + .clone() + .and_then(|deploy_config| deploy_config.start_command), + tcp_proxy_application_port: s.node.config.tcp_proxies.clone().and_then( + |tcp_proxies| tcp_proxies.first().map(|first| first.application_port), + ), + template: s.node.config.source.image.clone(), + variables: (!s_var.is_empty()).then_some(s_var), + volumes: (!s_vol.is_empty()).then_some(s_vol), + } + }) + .collect(); + dbg!(services.iter().for_each(|s| { + dbg!(s); + })); + + let vars = mutations::template_deploy::Variables { project_id: linked_project.project.clone(), - name: plugin.to_lowercase(), + environment_id: linked_project.environment.clone(), + services, + template_code: db.to_slug(), }; - if std::io::stdout().is_terminal() { - let spinner = indicatif::ProgressBar::new_spinner() - .with_style( - indicatif::ProgressStyle::default_spinner() - .tick_chars(TICK_STRING) - .template("{spinner:.green} {msg}")?, - ) - .with_message(format!("Creating {plugin}...")); - spinner.enable_steady_tick(Duration::from_millis(100)); - post_graphql::(&client, configs.get_backboard(), vars) - .await?; - spinner.finish_with_message(format!("Created {plugin}")); - } else { - println!("Creating {}...", plugin); - post_graphql::(&client, configs.get_backboard(), vars) + let created_plugin = + post_graphql::(&client, configs.get_backboard(), vars) .await?; - } + dbg!(created_plugin); } + /* + pub struct TemplateDeployService { + pub commit: Option, + #[serde(rename = "hasDomain")] + pub has_domain: Option, + #[serde(rename = "healthcheckPath")] + pub healthcheck_path: Option, + pub id: Option, + #[serde(rename = "isPrivate")] + pub is_private: Option, + pub name: Option, + pub owner: Option, + #[serde(rename = "rootDirectory")] + pub root_directory: Option, + #[serde(rename = "serviceIcon")] + pub service_icon: Option, + #[serde(rename = "serviceName")] + pub service_name: String, + #[serde(rename = "startCommand")] + pub start_command: Option, + #[serde(rename = "tcpProxyApplicationPort")] + pub tcp_proxy_application_port: Option, + pub template: String, + pub variables: Option, + pub volumes: Option>, + } + + */ + + // for plugin in selected { + // let vars = mutations::plugin_create::Variables { + // project_id: linked_project.project.clone(), + // name: plugin.to_lowercase(), + // }; + // if std::io::stdout().is_terminal() { + // let spinner = indicatif::ProgressBar::new_spinner() + // .with_style( + // indicatif::ProgressStyle::default_spinner() + // .tick_chars(TICK_STRING) + // .template("{spinner:.green} {msg}")?, + // ) + // .with_message(format!("Creating {plugin}...")); + // spinner.enable_steady_tick(Duration::from_millis(100)); + // post_graphql::(&client, configs.get_backboard(), vars) + // .await?; + // spinner.finish_with_message(format!("Created {plugin}")); + // } else { + // println!("Creating {}...", plugin); + // post_graphql::(&client, configs.get_backboard(), vars) + // .await?; + // } + // } + Ok(()) } diff --git a/src/controllers/database.rs b/src/controllers/database.rs index 8ed98f682..1622de246 100644 --- a/src/controllers/database.rs +++ b/src/controllers/database.rs @@ -8,3 +8,14 @@ pub enum DatabaseType { Redis, MongoDB, } + +impl DatabaseType { + pub fn to_slug(&self) -> String { + match self { + DatabaseType::PostgreSQL => "postgres".to_string(), + DatabaseType::MySQL => "mysql".to_string(), + DatabaseType::Redis => "redis".to_string(), + DatabaseType::MongoDB => "mongo".to_string(), + } + } +} diff --git a/src/gql/mutations/mod.rs b/src/gql/mutations/mod.rs index a36d26fe7..15342472a 100644 --- a/src/gql/mutations/mod.rs +++ b/src/gql/mutations/mod.rs @@ -1,4 +1,14 @@ use graphql_client::GraphQLQuery; +use serde::{Deserialize, Serialize}; +type TemplateDeployService = serde_json::Value; +type ServiceVariables = std::collections::BTreeMap; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateVolume { + pub mount_path: String, + pub name: Option, +} #[derive(GraphQLQuery)] #[graphql( @@ -47,3 +57,11 @@ pub struct ServiceDomainCreate; response_derives = "Debug, Serialize, Clone" )] pub struct ValidateTwoFactor; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/gql/schema.graphql", + query_path = "src/gql/mutations/strings/TemplateDeploy.graphql", + response_derives = "Debug, Serialize, Clone" +)] +pub struct TemplateDeploy; diff --git a/src/gql/mutations/strings/TemplateDeploy.graphql b/src/gql/mutations/strings/TemplateDeploy.graphql index 5609e6839..f4321cd3e 100644 --- a/src/gql/mutations/strings/TemplateDeploy.graphql +++ b/src/gql/mutations/strings/TemplateDeploy.graphql @@ -1,3 +1,8 @@ -mutation DatabaseDeploy($code: String!, $projectId: String!) { - templateDeploy(input) +mutation TemplateDeploy($projectId: String!, $environmentId: String!, $services: [TemplateDeployService!]!, $templateCode: String!) { + templateDeploy( + input: {projectId: $projectId, environmentId: $environmentId, services: $services, templateCode: $templateCode} + ) { + projectId + workflowId + } } \ No newline at end of file diff --git a/src/gql/queries/mod.rs b/src/gql/queries/mod.rs index c0f7d15d0..65dd2f613 100644 --- a/src/gql/queries/mod.rs +++ b/src/gql/queries/mod.rs @@ -1,8 +1,62 @@ use graphql_client::GraphQLQuery; +use serde::{Deserialize, Serialize}; type DateTime = chrono::DateTime; type ServiceVariables = std::collections::BTreeMap; +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceConfig { + pub name: String, + pub icon: Option, + pub source: TemplateServiceConfigIcon, + pub variables: Vec, + pub domains: Vec, + pub tcp_proxies: Option>, + // buildConfig is irrelevant + pub deploy_config: Option, + pub volumes: Option>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceDeployConfig { + pub start_command: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceVolumeConfig { + pub mount_path: String, + pub name: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceTcpProxy { + pub application_port: i64, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceDomainConfig { + pub has_service_domain: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TemplateServiceVariables { + pub name: String, + pub description: Option, + pub default_value: Option, + pub is_optional: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TemplateServiceConfigIcon { + pub image: String, +} + #[derive(GraphQLQuery)] #[graphql( schema_path = "src/gql/schema.graphql", From 504eeef70c238c9116cd084fca4f719a516295bb Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:01:21 +0000 Subject: [PATCH 15/20] fully support the add command --- src/commands/add.rs | 245 +++++++++++++++------------------------ src/errors.rs | 6 - src/gql/mutations/mod.rs | 4 +- 3 files changed, 96 insertions(+), 159 deletions(-) diff --git a/src/commands/add.rs b/src/commands/add.rs index 86abd423a..51909ad6f 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -5,9 +5,7 @@ use std::time::Duration; use strum::IntoEnumIterator; use crate::{ - consts::TICK_STRING, - controllers::{database::DatabaseType, project::get_project}, - mutations::TemplateVolume, + consts::TICK_STRING, controllers::database::DatabaseType, mutations::TemplateVolume, util::prompt::prompt_multi_options, }; @@ -27,8 +25,6 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { let client = GQLClient::new_authorized(&configs)?; let linked_project = configs.get_linked_project().await?; - let project = get_project(&client, &configs, linked_project.project.clone()).await?; - let databases = if args.database.is_empty() { if !std::io::stdout().is_terminal() { bail!("No plugins specified"); @@ -44,157 +40,104 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { for db in databases { // fetch template detail - let details = post_graphql::( - &client, - configs.get_backboard(), - queries::template_detail::Variables { code: db.to_slug() }, - ) - .await?; - let services: Vec = details - .template - .services - .edges - .iter() - .map(|s| { - let mut s_var = BTreeMap::::new(); - let mut s_vol = Vec::::new(); - // all variables in a db template have default values, this is safe - s.node.config.variables.iter().for_each(|v| { - s_var.insert(v.name.clone(), v.default_value.clone().unwrap()); - }); - if let Some(volumes) = s.node.config.volumes.clone() { - volumes.iter().for_each(|v| { - s_vol.push(TemplateVolume { - mount_path: v.mount_path.clone(), - name: v.name.clone(), - }) - }) - } - impl std::fmt::Debug for mutations::template_deploy::TemplateDeployService { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TemplateDeployService") - .field("commit", &self.commit) - .field("has_domain", &self.has_domain) - .field("healthcheck_path", &self.healthcheck_path) - .field("id", &self.id) - .field("is_private", &self.is_private) - .field("name", &self.name) - .field("owner", &self.owner) - .field("root_directory", &self.root_directory) - .field("service_icon", &self.service_icon) - .field("service_name", &self.service_name) - .field("start_command", &self.start_command) - .field( - "tcp_proxy_application_port", - &self.tcp_proxy_application_port, - ) - .field("template", &self.template) - .field("variables", &self.variables) - .field("volumes", &self.volumes) - .finish() - } + if std::io::stdout().is_terminal() { + let spinner = indicatif::ProgressBar::new_spinner() + .with_style( + indicatif::ProgressStyle::default_spinner() + .tick_chars(TICK_STRING) + .template("{spinner:.green} {msg}")?, + ) + .with_message(format!("Creating {db}...")); + spinner.enable_steady_tick(Duration::from_millis(100)); + fetch_and_create(&client, &configs, db.clone(), &linked_project).await?; + spinner.finish_with_message(format!("Created {db}")); + } else { + println!("Creating {}...", db); + fetch_and_create(&client, &configs, db, &linked_project).await?; + } + } + + Ok(()) +} +/// fetch database details via `TemplateDetail` +/// create database via `TemplateDeploy` +async fn fetch_and_create( + client: &reqwest::Client, + configs: &Configs, + db: DatabaseType, + linked_project: &LinkedProject, +) -> Result<(), anyhow::Error> { + let details = post_graphql::( + client, + configs.get_backboard(), + queries::template_detail::Variables { code: db.to_slug() }, + ) + .await?; + let services: Vec = details + .template + .services + .edges + .iter() + .map(|s| { + let mut s_var = BTreeMap::::new(); + let mut s_vol = Vec::::new(); + // all variables in a db template have default values, this is safe + for variable in s.node.config.variables.clone() { + s_var.insert( + variable.name.clone(), + variable.default_value.clone().unwrap(), + ); + } + if let Some(volumes) = s.node.config.volumes.clone() { + for volume in volumes { + s_vol.push(TemplateVolume { + mount_path: volume.mount_path.clone(), + name: volume.name.clone(), + }); } - mutations::template_deploy::TemplateDeployService { - commit: None, - has_domain: s - .node + } + + mutations::template_deploy::TemplateDeployService { + commit: None, + has_domain: Some( + s.node .config .domains .clone() .iter() .find(|d| d.has_service_domain) - .map(|s| s.has_service_domain), - healthcheck_path: None, - id: Some(s.node.id.clone()), - is_private: None, - name: Some(s.node.config.name.clone()), - owner: None, - root_directory: None, - service_icon: s.node.config.icon.clone(), - service_name: s.node.config.name.clone(), - start_command: s - .node - .config - .deploy_config - .clone() - .and_then(|deploy_config| deploy_config.start_command), - tcp_proxy_application_port: s.node.config.tcp_proxies.clone().and_then( - |tcp_proxies| tcp_proxies.first().map(|first| first.application_port), - ), - template: s.node.config.source.image.clone(), - variables: (!s_var.is_empty()).then_some(s_var), - volumes: (!s_vol.is_empty()).then_some(s_vol), - } - }) - .collect(); - dbg!(services.iter().for_each(|s| { - dbg!(s); - })); - - let vars = mutations::template_deploy::Variables { - project_id: linked_project.project.clone(), - environment_id: linked_project.environment.clone(), - services, - template_code: db.to_slug(), - }; - let created_plugin = - post_graphql::(&client, configs.get_backboard(), vars) - .await?; - dbg!(created_plugin); - } - - /* - pub struct TemplateDeployService { - pub commit: Option, - #[serde(rename = "hasDomain")] - pub has_domain: Option, - #[serde(rename = "healthcheckPath")] - pub healthcheck_path: Option, - pub id: Option, - #[serde(rename = "isPrivate")] - pub is_private: Option, - pub name: Option, - pub owner: Option, - #[serde(rename = "rootDirectory")] - pub root_directory: Option, - #[serde(rename = "serviceIcon")] - pub service_icon: Option, - #[serde(rename = "serviceName")] - pub service_name: String, - #[serde(rename = "startCommand")] - pub start_command: Option, - #[serde(rename = "tcpProxyApplicationPort")] - pub tcp_proxy_application_port: Option, - pub template: String, - pub variables: Option, - pub volumes: Option>, - } - - */ - - // for plugin in selected { - // let vars = mutations::plugin_create::Variables { - // project_id: linked_project.project.clone(), - // name: plugin.to_lowercase(), - // }; - // if std::io::stdout().is_terminal() { - // let spinner = indicatif::ProgressBar::new_spinner() - // .with_style( - // indicatif::ProgressStyle::default_spinner() - // .tick_chars(TICK_STRING) - // .template("{spinner:.green} {msg}")?, - // ) - // .with_message(format!("Creating {plugin}...")); - // spinner.enable_steady_tick(Duration::from_millis(100)); - // post_graphql::(&client, configs.get_backboard(), vars) - // .await?; - // spinner.finish_with_message(format!("Created {plugin}")); - // } else { - // println!("Creating {}...", plugin); - // post_graphql::(&client, configs.get_backboard(), vars) - // .await?; - // } - // } - + .map(|s| s.has_service_domain) + .unwrap_or(false), + ), + healthcheck_path: None, + id: Some(s.node.id.clone()), + is_private: None, + name: Some(s.node.config.name.clone()), + owner: None, + root_directory: None, + service_icon: s.node.config.icon.clone(), + service_name: s.node.config.name.clone(), + start_command: s + .node + .config + .deploy_config + .clone() + .and_then(|deploy_config| deploy_config.start_command), + tcp_proxy_application_port: s.node.config.tcp_proxies.clone().and_then( + |tcp_proxies| tcp_proxies.first().map(|first| first.application_port), + ), + template: s.node.config.source.image.clone(), + variables: (!s_var.is_empty()).then_some(s_var), + volumes: (!s_vol.is_empty()).then_some(s_vol), + } + }) + .collect(); + let vars = mutations::template_deploy::Variables { + project_id: linked_project.project.clone(), + environment_id: linked_project.environment.clone(), + services, + template_code: db.to_slug(), + }; + post_graphql::(client, configs.get_backboard(), vars).await?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index 31d7df8f5..59c2a045a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -44,15 +44,9 @@ pub enum RailwayError { #[error("No service linked\nRun `railway service` to link a service")] NoServiceLinked, - #[error("2FA code is incorrect. Please try again.")] - InvalidTwoFactorCode, - #[error("No command provided. Run with `railway run `")] NoCommandProvided, #[error("{0}")] FailedToUpload(String), - - #[error("Could not determine database type for service {0}")] - UnknownDatabaseType(String), } diff --git a/src/gql/mutations/mod.rs b/src/gql/mutations/mod.rs index 15342472a..f611ee28a 100644 --- a/src/gql/mutations/mod.rs +++ b/src/gql/mutations/mod.rs @@ -1,6 +1,5 @@ use graphql_client::GraphQLQuery; use serde::{Deserialize, Serialize}; -type TemplateDeployService = serde_json::Value; type ServiceVariables = std::collections::BTreeMap; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -62,6 +61,7 @@ pub struct ValidateTwoFactor; #[graphql( schema_path = "src/gql/schema.graphql", query_path = "src/gql/mutations/strings/TemplateDeploy.graphql", - response_derives = "Debug, Serialize, Clone" + response_derives = "Debug, Serialize, Clone", + skip_serializing_none )] pub struct TemplateDeploy; From 9b45ee7bcaff98de1b7fcab527b9ca34b06f72f9 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:07:04 +0000 Subject: [PATCH 16/20] rewrite some stuff --- src/commands/add.rs | 67 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/commands/add.rs b/src/commands/add.rs index 51909ad6f..703042f9b 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -11,7 +11,7 @@ use crate::{ use super::*; -/// Add a new plugin to your project +/// Provision a database into your project #[derive(Parser)] pub struct Args { /// The name of the database to add @@ -73,42 +73,45 @@ async fn fetch_and_create( queries::template_detail::Variables { code: db.to_slug() }, ) .await?; + let services: Vec = details .template .services .edges .iter() .map(|s| { - let mut s_var = BTreeMap::::new(); - let mut s_vol = Vec::::new(); - // all variables in a db template have default values, this is safe - for variable in s.node.config.variables.clone() { - s_var.insert( - variable.name.clone(), - variable.default_value.clone().unwrap(), - ); - } - if let Some(volumes) = s.node.config.volumes.clone() { - for volume in volumes { - s_vol.push(TemplateVolume { - mount_path: volume.mount_path.clone(), - name: volume.name.clone(), - }); - } - } + let s_var = s + .node + .config + .variables + .iter() + .map(|variable| { + ( + variable.name.clone(), + variable.default_value.clone().unwrap(), + ) + }) + .collect::>(); + + let s_vol = s + .node + .config + .volumes + .clone() + .map(|volumes| { + volumes + .into_iter() + .map(|volume| TemplateVolume { + mount_path: volume.mount_path.clone(), + name: volume.name.clone(), + }) + .collect::>() + }) + .unwrap_or_default(); mutations::template_deploy::TemplateDeployService { commit: None, - has_domain: Some( - s.node - .config - .domains - .clone() - .iter() - .find(|d| d.has_service_domain) - .map(|s| s.has_service_domain) - .unwrap_or(false), - ), + has_domain: Some(s.node.config.domains.iter().any(|d| d.has_service_domain)), healthcheck_path: None, id: Some(s.node.id.clone()), is_private: None, @@ -121,9 +124,9 @@ async fn fetch_and_create( .node .config .deploy_config - .clone() - .and_then(|deploy_config| deploy_config.start_command), - tcp_proxy_application_port: s.node.config.tcp_proxies.clone().and_then( + .as_ref() + .and_then(|deploy_config| deploy_config.start_command.clone()), + tcp_proxy_application_port: s.node.config.tcp_proxies.as_ref().and_then( |tcp_proxies| tcp_proxies.first().map(|first| first.application_port), ), template: s.node.config.source.image.clone(), @@ -132,12 +135,14 @@ async fn fetch_and_create( } }) .collect(); + let vars = mutations::template_deploy::Variables { project_id: linked_project.project.clone(), environment_id: linked_project.environment.clone(), services, template_code: db.to_slug(), }; + post_graphql::(client, configs.get_backboard(), vars).await?; Ok(()) } From 8159151de9422e8a66eac985f05393a7adce0e89 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:09:37 +0000 Subject: [PATCH 17/20] remove some stuff --- src/commands/add.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/add.rs b/src/commands/add.rs index 703042f9b..598e3eefa 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -27,7 +27,7 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { let databases = if args.database.is_empty() { if !std::io::stdout().is_terminal() { - bail!("No plugins specified"); + bail!("No database specified"); } prompt_multi_options("Select databases to add", DatabaseType::iter().collect())? } else { @@ -35,7 +35,7 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { }; if databases.is_empty() { - bail!("No plugins selected"); + bail!("No database selected"); } for db in databases { From 4c0c2fe423e4a440a4ea9f0aeccfa21547b92bb0 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:12:15 +0000 Subject: [PATCH 18/20] change some wording --- src/commands/connect.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/connect.rs b/src/commands/connect.rs index 246ddc469..8290d70b5 100644 --- a/src/commands/connect.rs +++ b/src/commands/connect.rs @@ -13,10 +13,10 @@ use crate::{controllers::project::get_service, queries::project::ProjectProjectS use super::*; -/// Connect to a plugin's shell (psql for Postgres, mongosh for MongoDB, etc.) +/// Connect to a database's shell (psql for Postgres, mongosh for MongoDB, etc.) #[derive(Parser)] pub struct Args { - /// The name of the plugin to connect to + /// The name of the database to connect to service_name: Option, /// Environment to pull variables from (defaults to linked environment) @@ -113,7 +113,7 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { Ok(()) } else { - bail!("No database found for service") + bail!("No supported database found in service") } } From e710ff86a9c296bcf57a295d8d10f87f01e54c79 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:19:58 +0000 Subject: [PATCH 19/20] fix connect command and remove old queries --- src/commands/connect.rs | 7 +++---- src/controllers/variables.rs | 1 - src/gql/queries/mod.rs | 8 -------- .../queries/strings/VariablesForPlugin.graphql | 11 ----------- src/util/prompt.rs | 15 --------------- 5 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 src/gql/queries/strings/VariablesForPlugin.graphql diff --git a/src/commands/connect.rs b/src/commands/connect.rs index 8290d70b5..629a1c82a 100644 --- a/src/commands/connect.rs +++ b/src/commands/connect.rs @@ -61,13 +61,12 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { })?; let environment_id = get_matched_environment(&project, environment)?.id; - let variables = get_service_variables( &client, &configs, linked_project.project, environment_id.clone(), - service.name, + service.id, ) .await?; let database_type = { @@ -118,13 +117,13 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { } fn get_connect_command( - plugin_type: DatabaseType, + database_type: DatabaseType, variables: BTreeMap, ) -> Result<(String, Vec)> { let pass_arg; // Hack to get ownership of formatted string outside match let default = &"".to_string(); - let (cmd_name, args): (&str, Vec<&str>) = match &plugin_type { + let (cmd_name, args): (&str, Vec<&str>) = match &database_type { DatabaseType::PostgreSQL => ( "psql", vec![variables.get("DATABASE_URL").unwrap_or(default)], diff --git a/src/controllers/variables.rs b/src/controllers/variables.rs index a82b85274..470d256a0 100644 --- a/src/controllers/variables.rs +++ b/src/controllers/variables.rs @@ -25,6 +25,5 @@ pub async fn get_service_variables( ) .await? .variables_for_service_deployment; - Ok(variables) } diff --git a/src/gql/queries/mod.rs b/src/gql/queries/mod.rs index 65dd2f613..971050b1e 100644 --- a/src/gql/queries/mod.rs +++ b/src/gql/queries/mod.rs @@ -107,14 +107,6 @@ pub struct UserProjects; )] pub struct VariablesForServiceDeployment; -#[derive(GraphQLQuery)] -#[graphql( - schema_path = "src/gql/schema.graphql", - query_path = "src/gql/queries/strings/VariablesForPlugin.graphql", - response_derives = "Debug, Serialize, Clone" -)] -pub struct VariablesForPlugin; - #[derive(GraphQLQuery)] #[graphql( schema_path = "src/gql/schema.graphql", diff --git a/src/gql/queries/strings/VariablesForPlugin.graphql b/src/gql/queries/strings/VariablesForPlugin.graphql deleted file mode 100644 index bb3872d0f..000000000 --- a/src/gql/queries/strings/VariablesForPlugin.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query VariablesForPlugin( - $projectId: String! - $environmentId: String! - $pluginId: String! -) { - variables( - projectId: $projectId - environmentId: $environmentId - pluginId: $pluginId - ) -} diff --git a/src/util/prompt.rs b/src/util/prompt.rs index 12f4be3c9..f7e420d0c 100644 --- a/src/util/prompt.rs +++ b/src/util/prompt.rs @@ -11,14 +11,6 @@ pub fn prompt_options(message: &str, options: Vec) -> Result { .context("Failed to prompt for options") } -pub fn prompt_confirm(message: &str) -> Result { - let confirm = inquire::Confirm::new(message); - confirm - .with_render_config(Configs::get_render_config()) - .prompt() - .context("Failed to prompt for confirm") -} - pub fn prompt_confirm_with_default(message: &str, default: bool) -> Result { let confirm = inquire::Confirm::new(message); confirm @@ -48,13 +40,6 @@ pub fn prompt_multi_options(message: &str, options: Vec) -> Resul .context("Failed to prompt for multi options") } -pub fn prompt_text(message: &str) -> Result { - let text = inquire::Text::new(message); - text.with_render_config(Configs::get_render_config()) - .prompt() - .context("Failed to prompt for text") -} - pub fn prompt_select(message: &str, options: Vec) -> Result { inquire::Select::new(message, options) .with_render_config(Configs::get_render_config()) From 9bc205eb4ae5be22906fe0295490a3f8464a4541 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Sun, 31 Mar 2024 19:09:13 +0100 Subject: [PATCH 20/20] remove wrong comment --- src/commands/add.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/add.rs b/src/commands/add.rs index 598e3eefa..b0a3d91b6 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -39,7 +39,6 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { } for db in databases { - // fetch template detail if std::io::stdout().is_terminal() { let spinner = indicatif::ProgressBar::new_spinner() .with_style(