Skip to content

Commit

Permalink
Merge pull request #192 from npocccties/feat-support-multiple-lms
Browse files Browse the repository at this point in the history
複数LMSのサポート
  • Loading branch information
kou029w committed Feb 4, 2021
2 parents 0bef663 + ab021b2 commit 9a97ec1
Show file tree
Hide file tree
Showing 56 changed files with 853 additions and 756 deletions.
1 change: 0 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
NEXT_PUBLIC_API_BASE_PATH=http://localhost:8080
NEXT_PUBLIC_LMS_URL=http://localhost:8081/
NEXT_PUBLIC_BASE_PATH=
PORT=8080
API_BASE_PATH=/api/v2
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GakuNinLMS-LTI-MC

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fnpocccties%2FChibiCHiLO&env=NEXT_PUBLIC_API_BASE_PATH,NEXT_PUBLIC_LMS_URL,API_BASE_PATH,FRONTEND_ORIGIN,FRONTEND_PATH,SESSION_SECRET,OAUTH_CONSUMER_KEY,OAUTH_CONSUMER_SECRET,DATABASE_URL)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fnpocccties%2FChibiCHiLO&env=NEXT_PUBLIC_API_BASE_PATH,API_BASE_PATH,FRONTEND_ORIGIN,FRONTEND_PATH,SESSION_SECRET,DATABASE_URL)

GakuNinLMS-LTI-MC, utilizing LTI, is a system that can create and browse learning contents using micro contents.

Expand Down Expand Up @@ -46,7 +46,6 @@ When changing the information of the connection destination of API, .env must be
| Environment variable | Explanation |
| --------------------------- | --------------------------------- |
| `NEXT_PUBLIC_API_BASE_PATH` | Base path for API URLs |
| `NEXT_PUBLIC_LMS_URL` | Learning management system URL |
| `NEXT_PUBLIC_BASE_PATH` | Base path for static content URLs |

## Build front-ends
Expand Down
18 changes: 18 additions & 0 deletions components/organisms/BookNotFoundProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Link from "@material-ui/core/Link";
import { useLmsUrl } from "$store/session";
import Problem from "./Problem";

export default function TopicNotFoundProblem() {
const url = useLmsUrl();

return (
<Problem title="トピックがありません">
トピックが見つかりませんでした
{url && (
<p>
<Link href={url}>LMSに戻る</Link>
</p>
)}
</Problem>
);
}
30 changes: 30 additions & 0 deletions components/organisms/Problem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default { title: "organisms/Problem" };

import Link from "@material-ui/core/Link";
import Problem from "./Problem";

export const Default = () => (
<Problem title="ブックが未連携です">
LTIリンクがどのブックとも連携されていません。担当教員にお問い合わせください
<p>
<Link href="#">LMSに戻る</Link>
</p>
</Problem>
);

// TODO: Please use <Provider> の問題の回避
function wrap(WrappedComponent: React.FC) {
function Component() {
return <WrappedComponent />;
}
return Component;
}

import UnlinkedProblem from "./UnlinkedProblem";
export const Unlinked = wrap(UnlinkedProblem);

import BookNotFoundProblem from "./BookNotFoundProblem";
export const BookNotFound = wrap(BookNotFoundProblem);

import TopicNotFoundProblem from "./TopicNotFoundProblem";
export const TopicNotFound = wrap(TopicNotFoundProblem);
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
import type { ReactNode } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Link from "@material-ui/core/Link";
import Container from "@material-ui/core/Container";
import useContainerStyles from "styles/container";
import { NEXT_PUBLIC_LMS_URL } from "$utils/env";

const useStyles = makeStyles((theme) => ({
container: {
marginTop: theme.spacing(4),
},
}));

type Props = { header: Node | string; children?: Node | string };
type Props = { title: ReactNode; children?: ReactNode };

export default function Unknown(props: Props) {
export default function Problem(props: Props) {
const classes = useStyles();
const containerClasses = useContainerStyles();
const { header, children } = props;
const { title, children } = props;
return (
<Container
classes={containerClasses}
className={classes.container}
maxWidth="md"
>
<Typography variant="h4" gutterBottom={true}>
{header}
</Typography>
<Typography variant="body1">
{children}
<br />
<Link href={NEXT_PUBLIC_LMS_URL}>LMSに戻る</Link>
{title}
</Typography>
<Typography variant="body1">{children}</Typography>
</Container>
);
}
18 changes: 18 additions & 0 deletions components/organisms/TopicNotFoundProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Link from "@material-ui/core/Link";
import { useLmsUrl } from "$store/session";
import Problem from "./Problem";

export default function BookNotFoundProblem() {
const url = useLmsUrl();

return (
<Problem title="ブックがありません">
ブックが見つかりませんでした
{url && (
<p>
<Link href={url}>LMSに戻る</Link>
</p>
)}
</Problem>
);
}
18 changes: 18 additions & 0 deletions components/organisms/UnlinkedProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Link from "@material-ui/core/Link";
import { useLmsUrl } from "$store/session";
import Problem from "./Problem";

export default function UnlinkedProblem() {
const url = useLmsUrl();

return (
<Problem title="ブックが未連携です">
LTIリンクがどのブックとも連携されていません。担当教員にお問い合わせください
{url && (
<p>
<Link href={url}>LMSに戻る</Link>
</p>
)}
</Problem>
);
}
9 changes: 0 additions & 9 deletions components/templates/Unknown.stories.tsx

This file was deleted.

1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ services:
image: node:lts-alpine
environment:
NEXT_PUBLIC_API_BASE_PATH: "http://localhost:8080"
NEXT_PUBLIC_LMS_URL: "http://localhost:8081"
NEXT_PUBLIC_BASE_PATH: ""
PORT: "8080"
API_BASE_PATH: /api/v2
Expand Down
56 changes: 38 additions & 18 deletions openapi/apis/DefaultApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,21 @@ export interface ApiV2LtiLaunchPostRequest {
resourceLinkTitle?: string;
contextTitle?: string;
lisPersonNameFull?: string;
launchPresentationReturnUrl?: string;
}

export interface ApiV2LtiResourceLinkLtiResourceLinkIdDeleteRequest {
export interface ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDeleteRequest {
ltiConsumerId: string;
ltiResourceLinkId: string;
}

export interface ApiV2LtiResourceLinkLtiResourceLinkIdGetRequest {
export interface ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGetRequest {
ltiConsumerId: string;
ltiResourceLinkId: string;
}

export interface ApiV2LtiResourceLinkLtiResourceLinkIdPutRequest {
export interface ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPutRequest {
ltiConsumerId: string;
ltiResourceLinkId: string;
body?: InlineObject;
}
Expand Down Expand Up @@ -510,6 +514,10 @@ export class DefaultApi extends runtime.BaseAPI {
formParams.append('lis_person_name_full', requestParameters.lisPersonNameFull as any);
}

if (requestParameters.launchPresentationReturnUrl !== undefined) {
formParams.append('launch_presentation_return_url', requestParameters.launchPresentationReturnUrl as any);
}

const response = await this.request({
path: `/api/v2/lti/launch`,
method: 'POST',
Expand All @@ -533,17 +541,21 @@ export class DefaultApi extends runtime.BaseAPI {
* LTI Resource Linkを削除します。 教員または管理者でなければなりません。
* LTI Resource Linkの削除
*/
async apiV2LtiResourceLinkLtiResourceLinkIdDeleteRaw(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdDeleteRequest): Promise<runtime.ApiResponse<object>> {
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDeleteRaw(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDeleteRequest): Promise<runtime.ApiResponse<object>> {
if (requestParameters.ltiConsumerId === null || requestParameters.ltiConsumerId === undefined) {
throw new runtime.RequiredError('ltiConsumerId','Required parameter requestParameters.ltiConsumerId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDelete.');
}

if (requestParameters.ltiResourceLinkId === null || requestParameters.ltiResourceLinkId === undefined) {
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiResourceLinkLtiResourceLinkIdDelete.');
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDelete.');
}

const queryParameters: runtime.HTTPQuery = {};

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({
path: `/api/v2/lti/resource_link/{lti_resource_link_id}`.replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
path: `/api/v2/lti/{lti_consumer_id}/resource_link/{lti_resource_link_id}`.replace(`{${"lti_consumer_id"}}`, encodeURIComponent(String(requestParameters.ltiConsumerId))).replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
Expand All @@ -556,26 +568,30 @@ export class DefaultApi extends runtime.BaseAPI {
* LTI Resource Linkを削除します。 教員または管理者でなければなりません。
* LTI Resource Linkの削除
*/
async apiV2LtiResourceLinkLtiResourceLinkIdDelete(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdDeleteRequest): Promise<object> {
const response = await this.apiV2LtiResourceLinkLtiResourceLinkIdDeleteRaw(requestParameters);
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDelete(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDeleteRequest): Promise<object> {
const response = await this.apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdDeleteRaw(requestParameters);
return await response.value();
}

/**
* LTI Resource Linkの詳細を取得します。 教員または管理者でなければなりません。
* LTI Resource Linkの取得
*/
async apiV2LtiResourceLinkLtiResourceLinkIdGetRaw(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdGetRequest): Promise<runtime.ApiResponse<InlineResponse200>> {
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGetRaw(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGetRequest): Promise<runtime.ApiResponse<InlineResponse200>> {
if (requestParameters.ltiConsumerId === null || requestParameters.ltiConsumerId === undefined) {
throw new runtime.RequiredError('ltiConsumerId','Required parameter requestParameters.ltiConsumerId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGet.');
}

if (requestParameters.ltiResourceLinkId === null || requestParameters.ltiResourceLinkId === undefined) {
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiResourceLinkLtiResourceLinkIdGet.');
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGet.');
}

const queryParameters: runtime.HTTPQuery = {};

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({
path: `/api/v2/lti/resource_link/{lti_resource_link_id}`.replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
path: `/api/v2/lti/{lti_consumer_id}/resource_link/{lti_resource_link_id}`.replace(`{${"lti_consumer_id"}}`, encodeURIComponent(String(requestParameters.ltiConsumerId))).replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
Expand All @@ -588,18 +604,22 @@ export class DefaultApi extends runtime.BaseAPI {
* LTI Resource Linkの詳細を取得します。 教員または管理者でなければなりません。
* LTI Resource Linkの取得
*/
async apiV2LtiResourceLinkLtiResourceLinkIdGet(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdGetRequest): Promise<InlineResponse200> {
const response = await this.apiV2LtiResourceLinkLtiResourceLinkIdGetRaw(requestParameters);
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGet(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGetRequest): Promise<InlineResponse200> {
const response = await this.apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdGetRaw(requestParameters);
return await response.value();
}

/**
* LTI Resource Linkを更新します。 教員または管理者でなければなりません。
* LTI Resource Linkの更新
*/
async apiV2LtiResourceLinkLtiResourceLinkIdPutRaw(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdPutRequest): Promise<runtime.ApiResponse<InlineResponse200>> {
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPutRaw(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPutRequest): Promise<runtime.ApiResponse<InlineResponse200>> {
if (requestParameters.ltiConsumerId === null || requestParameters.ltiConsumerId === undefined) {
throw new runtime.RequiredError('ltiConsumerId','Required parameter requestParameters.ltiConsumerId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPut.');
}

if (requestParameters.ltiResourceLinkId === null || requestParameters.ltiResourceLinkId === undefined) {
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiResourceLinkLtiResourceLinkIdPut.');
throw new runtime.RequiredError('ltiResourceLinkId','Required parameter requestParameters.ltiResourceLinkId was null or undefined when calling apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPut.');
}

const queryParameters: runtime.HTTPQuery = {};
Expand All @@ -609,7 +629,7 @@ export class DefaultApi extends runtime.BaseAPI {
headerParameters['Content-Type'] = 'application/json';

const response = await this.request({
path: `/api/v2/lti/resource_link/{lti_resource_link_id}`.replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
path: `/api/v2/lti/{lti_consumer_id}/resource_link/{lti_resource_link_id}`.replace(`{${"lti_consumer_id"}}`, encodeURIComponent(String(requestParameters.ltiConsumerId))).replace(`{${"lti_resource_link_id"}}`, encodeURIComponent(String(requestParameters.ltiResourceLinkId))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
Expand All @@ -623,8 +643,8 @@ export class DefaultApi extends runtime.BaseAPI {
* LTI Resource Linkを更新します。 教員または管理者でなければなりません。
* LTI Resource Linkの更新
*/
async apiV2LtiResourceLinkLtiResourceLinkIdPut(requestParameters: ApiV2LtiResourceLinkLtiResourceLinkIdPutRequest): Promise<InlineResponse200> {
const response = await this.apiV2LtiResourceLinkLtiResourceLinkIdPutRaw(requestParameters);
async apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPut(requestParameters: ApiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPutRequest): Promise<InlineResponse200> {
const response = await this.apiV2LtiLtiConsumerIdResourceLinkLtiResourceLinkIdPutRaw(requestParameters);
return await response.value();
}

Expand Down
8 changes: 8 additions & 0 deletions openapi/models/InlineResponse200.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import { exists, mapValues } from '../runtime';
* @interface InlineResponse200
*/
export interface InlineResponse200 {
/**
*
* @type {string}
* @memberof InlineResponse200
*/
consumerId: string;
/**
*
* @type {string}
Expand Down Expand Up @@ -61,6 +67,7 @@ export function InlineResponse200FromJSONTyped(json: any, ignoreDiscriminator: b
}
return {

'consumerId': json['consumerId'],
'id': json['id'],
'contextId': json['contextId'],
'contextTitle': json['contextTitle'],
Expand All @@ -78,6 +85,7 @@ export function InlineResponse200ToJSON(value?: InlineResponse200 | null): any {
}
return {

'consumerId': value.consumerId,
'id': value.id,
'contextId': value.contextId,
'contextTitle': value.contextTitle,
Expand Down
8 changes: 8 additions & 0 deletions openapi/models/InlineResponse2001Author.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface InlineResponse2001Author {
* @memberof InlineResponse2001Author
*/
id?: number;
/**
*
* @type {string}
* @memberof InlineResponse2001Author
*/
ltiConsumerId?: string;
/**
*
* @type {string}
Expand All @@ -50,6 +56,7 @@ export function InlineResponse2001AuthorFromJSONTyped(json: any, ignoreDiscrimin
return {

'id': !exists(json, 'id') ? undefined : json['id'],
'ltiConsumerId': !exists(json, 'ltiConsumerId') ? undefined : json['ltiConsumerId'],
'ltiUserId': !exists(json, 'ltiUserId') ? undefined : json['ltiUserId'],
'name': !exists(json, 'name') ? undefined : json['name'],
};
Expand All @@ -65,6 +72,7 @@ export function InlineResponse2001AuthorToJSON(value?: InlineResponse2001Author
return {

'id': value.id,
'ltiConsumerId': value.ltiConsumerId,
'ltiUserId': value.ltiUserId,
'name': value.name,
};
Expand Down

1 comment on commit 9a97ec1

@vercel
Copy link

@vercel vercel bot commented on 9a97ec1 Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.