Integrate graphql in website
This commit is contained in:
parent
577e144d0b
commit
be486b6da6
7 changed files with 4162 additions and 82 deletions
|
@ -15,4 +15,12 @@ module.exports = {
|
|||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: './src/graphql/generated.ts',
|
||||
rules: {
|
||||
'max-len': ['off']
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
14
website/codegen.yml
Normal file
14
website/codegen.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
schema: http://localhost:3000/api/website/graphql
|
||||
documents:
|
||||
- src/graphql/queries/get-prompt-info.ts
|
||||
generates:
|
||||
./src/graphql/generated.ts:
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-graphql-request
|
||||
config:
|
||||
avoidOptionals: true
|
||||
hooks:
|
||||
afterAllFileWrite:
|
||||
- eslint --fix
|
3919
website/package-lock.json
generated
3919
website/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -5,16 +5,24 @@
|
|||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"lint": "vue-cli-service lint",
|
||||
"generate": "graphql-codegen"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"graphql": "^15.4.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vuetify": "^2.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^1.20.0",
|
||||
"@graphql-codegen/typescript": "^1.20.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^3.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.13",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
|
115
website/src/graphql/generated.ts
Normal file
115
website/src/graphql/generated.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { GraphQLClient } from 'graphql-request';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export type Maybe<T> = T | null;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: string;
|
||||
String: string;
|
||||
Boolean: boolean;
|
||||
Int: number;
|
||||
Float: number;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
promptInfo: PromptInfo;
|
||||
};
|
||||
|
||||
export type QueryPromptInfoArgs = {
|
||||
promptId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type PromptInfo = {
|
||||
__typename?: 'PromptInfo';
|
||||
id: Scalars['String'];
|
||||
scopes: Array<Scalars['String']>;
|
||||
clientId: Scalars['String'];
|
||||
studentsMode: StudentsMode;
|
||||
application: PromptInfoApplication;
|
||||
};
|
||||
|
||||
export enum StudentsMode {
|
||||
None = 'None',
|
||||
One = 'One',
|
||||
Many = 'Many'
|
||||
}
|
||||
|
||||
export type PromptInfoApplication = {
|
||||
__typename?: 'PromptInfoApplication';
|
||||
name: Scalars['String'];
|
||||
iconUrl: Maybe<Scalars['String']>;
|
||||
iconColor: Scalars['String'];
|
||||
verified: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
login: LoginResult;
|
||||
};
|
||||
|
||||
export type MutationLoginArgs = {
|
||||
host: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
promptId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type LoginResult = {
|
||||
__typename?: 'LoginResult';
|
||||
encryptedPrivateKey: Scalars['String'];
|
||||
students: Array<LoginResultStudent>;
|
||||
};
|
||||
|
||||
export type LoginResultStudent = {
|
||||
__typename?: 'LoginResultStudent';
|
||||
studentId: Scalars['Int'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetPromptInfoQueryVariables = Exact<{
|
||||
promptId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type GetPromptInfoQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { promptInfo: (
|
||||
{ __typename?: 'PromptInfo' }
|
||||
& Pick<PromptInfo, 'scopes' | 'studentsMode'>
|
||||
& { application: (
|
||||
{ __typename?: 'PromptInfoApplication' }
|
||||
& Pick<PromptInfoApplication, 'name' | 'iconUrl' | 'iconColor' | 'verified'>
|
||||
); }
|
||||
); }
|
||||
);
|
||||
|
||||
export const GetPromptInfoDocument = gql`
|
||||
query GetPromptInfo($promptId: String!) {
|
||||
promptInfo(promptId: $promptId) {
|
||||
scopes
|
||||
studentsMode
|
||||
application {
|
||||
name
|
||||
iconUrl
|
||||
iconColor
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export type SdkFunctionWrapper = <T>(action: () => Promise<T>) => Promise<T>;
|
||||
|
||||
const defaultWrapper: SdkFunctionWrapper = (sdkFunction) => sdkFunction();
|
||||
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
|
||||
return {
|
||||
GetPromptInfo(variables: GetPromptInfoQueryVariables, requestHeaders?: Headers): Promise<GetPromptInfoQuery> {
|
||||
return withWrapper(() => client.request<GetPromptInfoQuery>(print(GetPromptInfoDocument), variables, requestHeaders));
|
||||
},
|
||||
};
|
||||
}
|
||||
export type Sdk = ReturnType<typeof getSdk>;
|
14
website/src/graphql/queries/get-prompt-info.ts
Normal file
14
website/src/graphql/queries/get-prompt-info.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
export default gql`query GetPromptInfo($promptId: String!) {
|
||||
promptInfo(promptId: $promptId) {
|
||||
scopes
|
||||
studentsMode
|
||||
application {
|
||||
name
|
||||
iconUrl
|
||||
iconColor
|
||||
verified
|
||||
}
|
||||
}
|
||||
}`;
|
|
@ -67,47 +67,66 @@
|
|||
</transition>
|
||||
</v-badge>
|
||||
</div>
|
||||
<div class="pt-16">
|
||||
<h2 class="text-subtitle-1 text--secondary mt-6 mb-2 px-4">
|
||||
Aplikacja
|
||||
<span class="text--primary">{{ promptInfo.application.name }}</span>
|
||||
chce uzyskać dostęp do twojego konta VULCAN UONET+ przez Wulkanowy Bridge
|
||||
</h2>
|
||||
<v-subheader>Uprawnienia aplikacji</v-subheader>
|
||||
<v-list subheader>
|
||||
<v-list-item v-for="item in scopeItems" :key="item.key">
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ item.title }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="item.subtitle !== undefined">
|
||||
{{ item.subtitle }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-alert color="info" text class="mb-2 mx-2">
|
||||
<span class="font-weight-medium">{{ promptInfo.application.name }}</span>
|
||||
nie zobaczy twojego hasła
|
||||
<template #append>
|
||||
<!-- TODO: Implement -->
|
||||
<v-btn text color="info">Więcej</v-btn>
|
||||
</template>
|
||||
</v-alert>
|
||||
<v-divider />
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" text outlined :href="denyUrl">
|
||||
Odmów
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary">
|
||||
Zaloguj się
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<v-window :value="step">
|
||||
<v-window-item :value="1">
|
||||
<div>
|
||||
<div class="pt-16">
|
||||
<h2 class="text-subtitle-1 text--secondary mt-6 mb-2 px-4">
|
||||
Aplikacja
|
||||
<span class="text--primary">{{ promptInfo.application.name }}</span>
|
||||
chce uzyskać dostęp do twojego konta VULCAN UONET+ przez Wulkanowy Bridge
|
||||
</h2>
|
||||
<v-subheader>Uprawnienia aplikacji</v-subheader>
|
||||
<v-list subheader>
|
||||
<v-list-item v-for="item in scopeItems" :key="item.key">
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ item.title }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="item.subtitle !== undefined">
|
||||
{{ item.subtitle }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-alert color="info" text class="mb-2 mx-2">
|
||||
<span class="font-weight-medium">{{ promptInfo.application.name }}</span>
|
||||
nie zobaczy twojego hasła
|
||||
<template #append>
|
||||
<!-- TODO: Implement -->
|
||||
<v-btn text color="info">Więcej</v-btn>
|
||||
</template>
|
||||
</v-alert>
|
||||
<v-divider />
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" text outlined :href="denyUrl">
|
||||
Odmów
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" @click="beginLogin">
|
||||
Dalej
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item :value="2">
|
||||
<div>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" text outlined @click="goBack">
|
||||
Wróć
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary">
|
||||
Zaloguj się
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-sheet>
|
||||
|
@ -144,6 +163,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { GraphQLClient } from 'graphql-request';
|
||||
import { getSdk } from '@/graphql/generated';
|
||||
|
||||
export enum StudentsMode {
|
||||
None = 'none',
|
||||
|
@ -174,36 +195,39 @@ export default class AuthenticatePromptApp extends Vue {
|
|||
|
||||
step = 1;
|
||||
|
||||
readonly scopeDescriptions: Record<string, {
|
||||
readonly scopeDescriptions: {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon: string;
|
||||
}> = {
|
||||
timetable: {
|
||||
}[] = [
|
||||
{
|
||||
key: 'timetable',
|
||||
title: 'Plan lekcji',
|
||||
icon: 'mdi-timetable',
|
||||
},
|
||||
grades: {
|
||||
{
|
||||
key: 'grades',
|
||||
title: 'Oceny i punkty',
|
||||
subtitle: 'Oceny cząstkowe, końcowe, opisowe oraz punkty',
|
||||
icon: 'mdi-numeric-6-box-multiple-outline',
|
||||
},
|
||||
notes: {
|
||||
title: 'Uwagi',
|
||||
{
|
||||
key: 'notes',
|
||||
title: 'Uwagi i pochwały',
|
||||
icon: 'mdi-note-text-outline',
|
||||
},
|
||||
achievements: {
|
||||
{
|
||||
key: 'achievements',
|
||||
title: 'Osiągnięcia',
|
||||
icon: 'mdi-trophy-outline',
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
get scopeItems() {
|
||||
if (this.promptInfo === null) return undefined;
|
||||
return this.promptInfo.scopes.map((key: string) => ({
|
||||
key,
|
||||
...this.scopeDescriptions[key],
|
||||
}));
|
||||
return this.scopeDescriptions
|
||||
.filter(({ key }) => this.promptInfo?.scopes?.includes(key) ?? false);
|
||||
}
|
||||
|
||||
get denyUrl() {
|
||||
|
@ -214,20 +238,20 @@ export default class AuthenticatePromptApp extends Vue {
|
|||
async loadPromptInfo() {
|
||||
this.promptInfoError = false;
|
||||
this.promptInfo = null;
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
if (Math.random() < 0.5) {
|
||||
|
||||
if (!this.promptId) return;
|
||||
|
||||
const client = new GraphQLClient('/api/website/graphql');
|
||||
const sdk = getSdk(client);
|
||||
|
||||
try {
|
||||
const { promptInfo } = await sdk.GetPromptInfo({
|
||||
promptId: this.promptId,
|
||||
});
|
||||
this.promptInfo = promptInfo;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.promptInfoError = true;
|
||||
} else {
|
||||
this.promptInfo = {
|
||||
scopes: ['grades', 'timetable', 'notes', 'achievements'],
|
||||
studentsMode: StudentsMode.Many,
|
||||
application: {
|
||||
iconColor: '#f00',
|
||||
iconUrl: null,
|
||||
name: 'Not a fancy app',
|
||||
verified: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,5 +261,13 @@ export default class AuthenticatePromptApp extends Vue {
|
|||
if (!this.promptId) return;
|
||||
await this.loadPromptInfo();
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.step -= 1;
|
||||
}
|
||||
|
||||
beginLogin() {
|
||||
this.step = 2;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue