Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

複数LMSのサポート #192

Merged
merged 14 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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