Skip to content

Commit

Permalink
Merge pull request #19 from hwgilbert16/develop
Browse files Browse the repository at this point in the history
v1.0.3 Release - Anki imports + media support
  • Loading branch information
hwgilbert16 authored Jul 6, 2023
2 parents 03a84a0 + 0e6811c commit dd24423
Show file tree
Hide file tree
Showing 86 changed files with 28,383 additions and 22,690 deletions.
11 changes: 11 additions & 0 deletions .env.compose.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ JWT_SECRET=
# Port to expose Scholarsome on
HTTP_PORT=

# Data storage configuration
# If local, file storage will be managed by Docker Compose
STORAGE_TYPE=

# Required if storage type is s3
S3_STORAGE_ENDPOINT=
S3_STORAGE_ACCESS_KEY=
S3_STORAGE_SECRET_KEY=
S3_STORAGE_REGION=
S3_STORAGE_BUCKET=


# ---
# Everything past this line is optional
Expand Down
14 changes: 14 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ REDIS_PORT=
REDIS_USERNAME=
REDIS_PASSWORD=

# Data storage configuration
STORAGE_TYPE=

# Required if storage type is local
# Absolute filepath
STORAGE_LOCAL_DIR=

# Required if storage type is s3
S3_STORAGE_ENDPOINT=
S3_STORAGE_ACCESS_KEY=
S3_STORAGE_SECRET_KEY=
S3_STORAGE_REGION=
S3_STORAGE_BUCKET=


# ---
# Everything past this line is optional
Expand Down
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ REDIS_PORT=
REDIS_USERNAME=
REDIS_PASSWORD=

# Data storage configuration
STORAGE_TYPE=

# Required if storage type is local
# Absolute filepath
STORAGE_LOCAL_DIR=

# Required if storage type is s3
S3_STORAGE_ENDPOINT=
S3_STORAGE_ACCESS_KEY=
S3_STORAGE_SECRET_KEY=
S3_STORAGE_REGION=
S3_STORAGE_BUCKET=


# ---
# Everything past this line is optional
Expand Down
49 changes: 29 additions & 20 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# syntax=docker/dockerfile:1.3-labs
FROM node:18

ARG NODE_ENV
ARG DATABASE_PASSWORD
ARG DATABASE_URL
ARG JWT_SECRET
ARG HTTP_PORT

ARG SMTP_HOST
ARG SMTP_PORT
Expand All @@ -26,25 +28,32 @@ COPY . .
RUN npm run generate
RUN npm run build

RUN touch .env

RUN echo "NODE_ENV=$NODE_ENV\n" >> .env
RUN echo "DATABASE_PASSWORD=$DATABASE_PASSWORD\n" >> .env
RUN echo "DATABASE_URL=$DATABASE_URL\n" >> .env
RUN echo "JWT_SECRET=$JWT_SECRET\n" >> .env

RUN echo "SMTP_HOST=$SMTP_HOST\n" >> .env
RUN echo "SMTP_PORT=$SMTP_PORT\n" >> .env
RUN echo "SMTP_USERNAME=$SMTP_USERNAME\n" >> .env
RUN echo "SMTP_PASSWORD=$SMTP_PASSWORD\n" >> .env
RUN echo "HOST=$HOST\n" >> .env
RUN echo "SSL_KEY_BASE64=$SSL_KEY_BASE64\n" >> .env
RUN echo "SSL_CERT_BASE64=$SSL_CERT_BASE64\n" >> .env
RUN echo "SCHOLARSOME_RECAPTCHA_SITE=$SCHOLARSOME_RECAPTCHA_SITE\n" >> .env
RUN echo "SCHOLARSOME_RECAPTCHA_SECRET=$SCHOLARSOME_RECAPTCHA_SECRET\n" >> .env
RUN echo "REDIS_HOST=$REDIS_HOST" >> .env
RUN echo "REDIS_PORT=$REDIS_PORT" >> .env
RUN echo "REDIS_USERNAME=$REDIS_USERNAME" >> .env
RUN echo "REDIS_PASSWORD=$REDIS_PASSWORD" >> .env
RUN <<EOL
touch .env
echo "NODE_ENV=$NODE_ENV\n" >> .env
echo "DATABASE_PASSWORD=$DATABASE_PASSWORD\n" >> .env
echo "DATABASE_URL=$DATABASE_URL\n" >> .env
echo "JWT_SECRET=$JWT_SECRET\n" >> .env
echo "HTTP_PORT=$HTTP_PORT\n" >> .env

echo "S3_STORAGE_ENDPOINT=$S3_STORAGE_ENDPOINT\n" >> .env
echo "S3_STORAGE_ACCESS_KEY=$S3_STORAGE_ACCESS_KEY\n" >> .env
echo "S3_STORAGE_SECRET_KEY=$S3_STORAGE_SECRET_KEY\n" >> .env
echo "S3_STORAGE_REGION=$S3_STORAGE_REGION\n" >> .env
echo "S3_STORAGE_BUCKET=$S3_STORAGE_BUCKET\n" >> .env
echo "SMTP_HOST=$SMTP_HOST\n" >> .env
echo "SMTP_PORT=$SMTP_PORT\n" >> .env
echo "SMTP_USERNAME=$SMTP_USERNAME\n" >> .env
echo "SMTP_PASSWORD=$SMTP_PASSWORD\n" >> .env
echo "HOST=$HOST\n" >> .env
echo "SSL_KEY_BASE64=$SSL_KEY_BASE64\n" >> .env
echo "SSL_CERT_BASE64=$SSL_CERT_BASE64\n" >> .env
echo "SCHOLARSOME_RECAPTCHA_SITE=$SCHOLARSOME_RECAPTCHA_SITE\n" >> .env
echo "SCHOLARSOME_RECAPTCHA_SECRET=$SCHOLARSOME_RECAPTCHA_SECRET\n" >> .env
echo "REDIS_HOST=$REDIS_HOST" >> .env
echo "REDIS_PORT=$REDIS_PORT" >> .env
echo "REDIS_USERNAME=$REDIS_USERNAME" >> .env
echo "REDIS_PASSWORD=$REDIS_PASSWORD" >> .env
EOL

CMD [ "npm", "run", "serve:node" ]
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## <p align="center"><img src="https://raw.githubusercontent.com/hwgilbert16/scholarsome/develop/apps/front/src/assets/scholarsome-logo-purple-lowercase.svg" height="50%" width="50%"></p>
## <p align="center"><img src="https://raw.githubusercontent.com/hwgilbert16/scholarsome/develop/apps/front/src/assets/header/scholarsome-logo-purple-lowercase.svg" height="50%" width="50%"></p>

<div align="center">

Expand All @@ -8,6 +8,8 @@

https://scholarsome.com

<a href="https://discord.gg/hRgVvc5MKf">![](https://img.shields.io/badge/-Join%20our%20Discord-white?style=flat&logo=Discord&logoColor=blue)</a>

<a href="">![](https://img.shields.io/github/license/hwgilbert16/scholarsome?style=flat-square&color=blue)</a>
<a href="">![](https://img.shields.io/badge/contributions-welcome-orange?style=flat-square)</a>
<a href="">![](https://img.shields.io/github/issues/hwgilbert16/scholarsome?style=flat-square)</a>
Expand All @@ -20,7 +22,7 @@ https://scholarsome.com

## Introduction

Scholarsome is an open source studying system. Through the use of flashcards, among other core features, users can practice memorization of terms and definitions, along with keeping their data secure locally.
Scholarsome <a href="http://ipa-reader.xyz/?text=%CB%88sk%C3%A4l%C9%99rs(%C9%99)m%2F">(pronounced ˈskälərs(ə)m/)</a> is an open source studying system. Through the use of flashcards, among other core features, users can practice memorization of terms and definitions, along with keeping their data secure locally.

While other services have begun to paywall core functionalities, Scholarsome intends to offer an equal alternative that does not compromise on feature sets.

Expand All @@ -29,9 +31,9 @@ While other services have begun to paywall core functionalities, Scholarsome int
Implemented features include:

- Create your own study sets ✅
- Import sets from Anki and Quizlet ✅
- Study flashcards in either traditional or progressive mode ✅
- Use quizzes to test yourself ✅
- Import created sets from Quizlet ✅
- Edit your sets on the fly ✅
- Make sets private if studying with others ✅

Expand Down
4 changes: 3 additions & 1 deletion apps/api/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
"rules": {
"no-control-regex": "off"
}
},
{
"files": ["*.js", "*.jsx"],
Expand Down
5 changes: 4 additions & 1 deletion apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RedisModule } from "@liaoliaots/nestjs-redis";
import { JwtModule } from "@nestjs/jwt";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { GlobalInterceptor } from "./auth/global.interceptor";
import { MediaModule } from "./media/media.module";

@Module({
imports: [
Expand All @@ -26,7 +27,8 @@ import { GlobalInterceptor } from "./auth/global.interceptor";
serveStaticOptions: {
cacheControl: true,
maxAge: 31536000
}
},
exclude: ["/api/(.*)"]
}),
ConfigModule.forRoot({
isGlobal: true
Expand All @@ -48,6 +50,7 @@ import { GlobalInterceptor } from "./auth/global.interceptor";
MailModule,
CardsModule,
UsersModule,
MediaModule,
{
...JwtModule.registerAsync({
useFactory: (configService: ConfigService) => ({
Expand Down
81 changes: 80 additions & 1 deletion apps/api/src/app/cards/cards.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,35 @@ export class CardsController {
@UseGuards(AuthenticatedGuard, CreateCardGuard)
@Post()
async createCard(@Body() body: CreateCardDto): Promise<ApiResponse<Card>> {
let media = [];

const termScanned = await this.cardsService.scanAndUploadMedia(body.term, body.setId);
if (termScanned) {
body.term = termScanned.scanned;
media = [...termScanned.media];
}

const definitionScanned = await this.cardsService.scanAndUploadMedia(body.definition, body.setId);
if (definitionScanned) {
body.definition = definitionScanned.scanned;
media = [...media, ...definitionScanned.media];
}

return {
status: "success",
data: await this.cardsService.createCard({
index: body.index,
term: body.term,
definition: body.definition,
media: {
createMany: {
data: media.map((c) => {
return {
name: c
};
})
}
},
set: {
connect: {
id: body.setId
Expand All @@ -102,6 +125,53 @@ export class CardsController {
@UseGuards(AuthenticatedGuard, UpdateCardGuard)
@Put(":cardId")
async updateCard(@Param() params: CardIdParam, @Body() body: UpdateCardDto): Promise<ApiResponse<Card>> {
const card = await this.cardsService.card({ id: params.cardId });
if (!card) throw new NotFoundException();

let media = [];

// there's likely a better way to write the media deletion checking
// but for now the implementation here is sufficient
let mediaChecked = false;

if (body.term) {
const termScanned = await this.cardsService.scanAndUploadMedia(body.term, card.setId);
if (termScanned) {
body.term = termScanned.scanned;
media = [...termScanned.media];
}

for (const media of card.media) {
mediaChecked = true;

if (!body.definition && !body.term.includes(media.name)) {
await this.cardsService.deleteMedia(card.setId, media.name);
} else if (
body.definition &&
!body.definition.includes(media.name) &&
!body.term.includes(media.name)
) {
await this.cardsService.deleteMedia(card.setId, media.name);
}
}
}

if (body.definition) {
const definitionScanned = await this.cardsService.scanAndUploadMedia(body.definition, card.setId);
if (definitionScanned) {
body.definition = definitionScanned.scanned;
media = [...media, ...definitionScanned.media];
}

if (!mediaChecked) {
for (const media of card.media) {
if (!body.definition.includes(media.name)) {
await this.cardsService.deleteMedia(card.setId, media.name);
}
}
}
}

return {
status: "success",
data: await this.cardsService.updateCard({
Expand All @@ -111,7 +181,16 @@ export class CardsController {
data: {
index: body.index,
term: body.term,
definition: body.definition
definition: body.definition,
media: {
createMany: {
data: media.map((c) => {
return {
name: c
};
})
}
}
}
})
};
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/cards/cards.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Module } from "@nestjs/common";
import { forwardRef, Module } from "@nestjs/common";
import { DatabaseModule } from "../providers/database/database.module";
import { CardsController } from "./cards.controller";
import { CardsService } from "./cards.service";
import { SetsModule } from "../sets/sets.module";
import { UsersModule } from "../users/users.module";

@Module({
imports: [DatabaseModule, SetsModule, UsersModule],
imports: [DatabaseModule, UsersModule, forwardRef(() => SetsModule)],
controllers: [CardsController],
providers: [CardsService],
exports: [CardsService]
Expand Down
Loading

0 comments on commit dd24423

Please sign in to comment.