Implement app creation
This commit is contained in:
parent
8f47ee3aec
commit
b75a31a201
8 changed files with 191 additions and 11 deletions
|
@ -11,6 +11,7 @@ import CreateUserResolver from './resolvers/authenticate-prompt/create-user-reso
|
|||
import LoginResolver from './resolvers/authenticate-prompt/login-resolver';
|
||||
import PromptInfoResolver from './resolvers/authenticate-prompt/prompt-info-resolver';
|
||||
import SetSymbolResolver from './resolvers/authenticate-prompt/set-symbol-resolver';
|
||||
import CreateApplicationResolver from './resolvers/developer/create-application';
|
||||
import LoginStateResolver from './resolvers/developer/get-login-state';
|
||||
import type { WebsiteAPIContext } from './types';
|
||||
|
||||
|
@ -23,6 +24,7 @@ export default async function registerWebsiteApi(server: MyFastifyInstance): Pro
|
|||
SetSymbolResolver,
|
||||
CreateUserResolver,
|
||||
LoginStateResolver,
|
||||
CreateApplicationResolver,
|
||||
],
|
||||
});
|
||||
const apolloServer = new ApolloServer({
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { Field, ObjectType } from 'type-graphql';
|
||||
|
||||
@ObjectType()
|
||||
export default class ApplicationInfo {
|
||||
@Field(() => String)
|
||||
public id!: string;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import { UserInputError } from 'apollo-server-fastify';
|
||||
import {
|
||||
Arg, Ctx, Mutation, Resolver, UnauthorizedError,
|
||||
} from 'type-graphql';
|
||||
import Application from '../../../../database/entities/application';
|
||||
import ApplicationInfo from '../../models/application-info';
|
||||
import type { WebsiteAPIContext } from '../../types';
|
||||
|
||||
@Resolver(() => ApplicationInfo)
|
||||
export default class CreateApplicationResolver {
|
||||
@Mutation(() => ApplicationInfo)
|
||||
public async createApplication(
|
||||
@Arg('name') name: string,
|
||||
@Ctx() { sessionData }: WebsiteAPIContext,
|
||||
): Promise<ApplicationInfo> {
|
||||
if (!sessionData.loginState) throw new UnauthorizedError();
|
||||
if (name !== name.trim()) throw new UserInputError('Name should be trimmed');
|
||||
if (name.trim().length < 3) throw new UserInputError('Name is too short');
|
||||
if (name.trim().length > 32) throw new UserInputError('Name is too long');
|
||||
|
||||
const application = new Application();
|
||||
application.developerId = sessionData.loginState.developerId;
|
||||
application.homepage = null;
|
||||
application.verified = false;
|
||||
application.iconUrl = null;
|
||||
application.iconColor = '#444444';
|
||||
application.name = name;
|
||||
await application.save();
|
||||
|
||||
return {
|
||||
id: application._id.toHexString(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ documents:
|
|||
- src/graphql/mutations/login.ts
|
||||
- src/graphql/mutations/set-symbol.ts
|
||||
- src/graphql/mutations/create-user.ts
|
||||
- src/graphql/mutations/create-application.ts
|
||||
generates:
|
||||
./src/graphql/generated.ts:
|
||||
plugins:
|
||||
|
|
91
website/src/compontents/developer/new-app-dialog.vue
Normal file
91
website/src/compontents/developer/new-app-dialog.vue
Normal file
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<v-dialog v-model="value" max-width="450" :persistent="loading">
|
||||
<template #activator="{ on }">
|
||||
<slot :on="on" name="activator" />
|
||||
</template>
|
||||
<v-card>
|
||||
<v-form @submit.prevent="submit">
|
||||
<v-card-title>
|
||||
Create new application
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-4">
|
||||
<v-text-field
|
||||
label="Application name"
|
||||
hint="Users will see this name"
|
||||
persistent-hint
|
||||
autofocus
|
||||
outlined
|
||||
counter="32"
|
||||
:counter-value="(v) => v.trim().length"
|
||||
v-model="name"
|
||||
/>
|
||||
<v-alert type="error" class="mb-0 mt-2" :value="error">
|
||||
An error occured
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="primary"
|
||||
text
|
||||
@click="value = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:disabled="!valid"
|
||||
:loading="loading"
|
||||
type="submit"
|
||||
>
|
||||
Create
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator';
|
||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
||||
|
||||
@Component({
|
||||
name: 'NewAppDialog',
|
||||
})
|
||||
export default class NewAppDialog extends Vue {
|
||||
value = false;
|
||||
|
||||
name = '';
|
||||
|
||||
loading = false;
|
||||
|
||||
error = false;
|
||||
|
||||
@Watch('value')
|
||||
valueChanged(value: boolean) {
|
||||
if (!value) this.name = '';
|
||||
}
|
||||
|
||||
get valid() {
|
||||
return this.name.trim().length >= 3 && this.name.trim().length <= 32;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.valid || this.loading) return;
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
try {
|
||||
const result = await sdk.CreateApplication({
|
||||
name: this.name.trim(),
|
||||
});
|
||||
await this.$router.push(`/apps/${result.createApplication.id}`);
|
||||
this.value = false;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.error = true;
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
</script>t
|
|
@ -68,6 +68,7 @@ export type Mutation = {
|
|||
createUser: CreateUserResult;
|
||||
login: LoginResult;
|
||||
setSymbol: SetSymbolResult;
|
||||
createApplication: ApplicationInfo;
|
||||
};
|
||||
|
||||
export type MutationCreateUserArgs = {
|
||||
|
@ -88,6 +89,10 @@ export type MutationSetSymbolArgs = {
|
|||
promptId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type MutationCreateApplicationArgs = {
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateUserResult = {
|
||||
__typename?: 'CreateUserResult';
|
||||
success: Scalars['Boolean'];
|
||||
|
@ -110,6 +115,23 @@ export type LoginStudent = {
|
|||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ApplicationInfo = {
|
||||
__typename?: 'ApplicationInfo';
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateApplicationMutationVariables = Exact<{
|
||||
name: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type CreateApplicationMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { createApplication: (
|
||||
{ __typename?: 'ApplicationInfo' }
|
||||
& Pick<ApplicationInfo, 'id'>
|
||||
); }
|
||||
);
|
||||
|
||||
export type CreateUserMutationVariables = Exact<{
|
||||
promptId: Scalars['String'];
|
||||
email: Scalars['String'];
|
||||
|
@ -186,6 +208,13 @@ export type GetPromptInfoQuery = (
|
|||
); }
|
||||
);
|
||||
|
||||
export const CreateApplicationDocument = gql`
|
||||
mutation CreateApplication($name: String!) {
|
||||
createApplication(name: $name) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const CreateUserDocument = gql`
|
||||
mutation CreateUser($promptId: String!, $email: String!) {
|
||||
createUser(promptId: $promptId, email: $email) {
|
||||
|
@ -253,6 +282,9 @@ export type SdkFunctionWrapper = <T>(action: () => Promise<T>) => Promise<T>;
|
|||
const defaultWrapper: SdkFunctionWrapper = (sdkFunction) => sdkFunction();
|
||||
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
|
||||
return {
|
||||
CreateApplication(variables: CreateApplicationMutationVariables, requestHeaders?: Headers): Promise<CreateApplicationMutation> {
|
||||
return withWrapper(() => client.request<CreateApplicationMutation>(print(CreateApplicationDocument), variables, requestHeaders));
|
||||
},
|
||||
CreateUser(variables: CreateUserMutationVariables, requestHeaders?: Headers): Promise<CreateUserMutation> {
|
||||
return withWrapper(() => client.request<CreateUserMutation>(print(CreateUserDocument), variables, requestHeaders));
|
||||
},
|
||||
|
|
8
website/src/graphql/mutations/create-application.ts
Normal file
8
website/src/graphql/mutations/create-application.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
export default gql`mutation CreateApplication($name: String!) {
|
||||
createApplication(name: $name) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -4,16 +4,19 @@
|
|||
Your applications
|
||||
</div>
|
||||
<div class="applications">
|
||||
<v-card
|
||||
link
|
||||
outlined
|
||||
class="d-flex flex-column align-center justify-center text-center"
|
||||
color="primary--text"
|
||||
to="/new-app"
|
||||
>
|
||||
<v-icon color="primary" :size="64">mdi-plus</v-icon>
|
||||
<div class="text-h5 my-2">New app</div>
|
||||
</v-card>
|
||||
<new-app-dialog>
|
||||
<template #activator="{ on }">
|
||||
<v-card
|
||||
outlined
|
||||
class="d-flex flex-column align-center justify-center text-center"
|
||||
color="primary--text"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon color="primary" :size="64">mdi-plus</v-icon>
|
||||
<div class="text-h5 my-2">New app</div>
|
||||
</v-card>
|
||||
</template>
|
||||
</new-app-dialog>
|
||||
<v-card
|
||||
v-for="i in 18"
|
||||
:key="i"
|
||||
|
@ -51,10 +54,11 @@
|
|||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import AppIcon from '@/pages/app-icon.vue';
|
||||
import NewAppDialog from '@/compontents/developer/new-app-dialog.vue';
|
||||
|
||||
@Component({
|
||||
name: 'DeveloperHome',
|
||||
components: { AppIcon },
|
||||
components: { NewAppDialog, AppIcon },
|
||||
})
|
||||
export default class DeveloperHome extends Vue {
|
||||
|
||||
|
|
Loading…
Reference in a new issue