Implement app creation

This commit is contained in:
Dominik Korsa 2021-02-12 21:23:51 +01:00
parent 8f47ee3aec
commit b75a31a201
No known key found for this signature in database
GPG key ID: 546F986F71A6FE6E
8 changed files with 191 additions and 11 deletions

View file

@ -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({

View file

@ -0,0 +1,7 @@
import { Field, ObjectType } from 'type-graphql';
@ObjectType()
export default class ApplicationInfo {
@Field(() => String)
public id!: string;
}

View file

@ -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(),
};
}
}

View file

@ -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:

View 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

View file

@ -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));
},

View file

@ -0,0 +1,8 @@
import gql from 'graphql-tag';
export default gql`mutation CreateApplication($name: String!) {
createApplication(name: $name) {
id
}
}
`;

View file

@ -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 {