4 |
5 |
9 |
10 |
11 | > See how _the exact same_ Medium.com clone is built using different [frontends](https://codebase.show/projects/realworld?category=frontend) and [backends](https://codebase.show/projects/realworld?category=backend). Yes, you can mix and match them, because **they all adhere to the same [API spec](specs/backend-specs/introduction)** 😮😎
12 |
13 | While most "todo" demos provide an excellent cursory glance at a framework's capabilities, they typically don't convey the knowledge & perspective required to actually build _real_ applications with it.
14 |
15 | **RealWorld** solves this by allowing you to choose any frontend (React, Angular, & more) and any backend (Node, Django, & more).
16 |
17 | _Read the [full blog post announcing RealWorld on Medium.](https://medium.com/@ericsimons/introducing-realworld-6016654d36b5)_
18 |
19 | Join us on [GitHub Discussions!](https://github.com/gothinkster/realworld/discussions) 🎉
20 |
21 | ## Implementations
22 |
23 | Over 150 implementations have been created using various languages, libraries, and frameworks.
24 |
25 | Explore them on [**CodebaseShow**](https://codebase.show/projects/realworld).
26 |
27 | ## Create a new implementation
28 |
29 | [**Create a new implementation >>>**](implementation-creation/introduction)
30 |
31 | Or you can [view upcoming implementations (WIPs)](https://github.com/gothinkster/realworld/discussions/categories/wip-implementations).
32 |
33 | ## Learn more
34 |
35 | - ["Introducing RealWorld 🙌"](https://medium.com/@ericsimons/introducing-realworld-6016654d36b5) by Eric Simons
36 | - Every tutorial is built against the same [API spec](/specifications/backend/introduction) to ensure modularity of every frontend & backend
37 | - Every frontend utilizes the same hand crafted [Bootstrap 4 theme](https://github.com/gothinkster/conduit-bootstrap-template) for identical UI/UX
38 | - There is a [hosted version](https://realworld-docs.netlify.app/docs/specs/frontend-specs/api#demo-api) of the backend API available for public usage, no API keys are required
39 | - Interested in creating a new RealWorld stack? View our [starter guide & spec](implementation-creation/introduction)
40 |
--------------------------------------------------------------------------------
/apps/api/server/routes/api/users/index.post.ts:
--------------------------------------------------------------------------------
1 | import HttpException from "~/models/http-exception.model";
2 | import {default as bcrypt} from 'bcryptjs';
3 |
4 | export default defineEventHandler(async (event) => {
5 | const {user} = await readBody(event);
6 |
7 | const email = user.email?.trim();
8 | const username = user.username?.trim();
9 | const password = user.password?.trim();
10 | const {image, bio, demo} = user;
11 |
12 | if (!email) {
13 | throw new HttpException(422, {errors: {email: ["can't be blank"]}});
14 | }
15 |
16 | if (!username) {
17 | throw new HttpException(422, {errors: {username: ["can't be blank"]}});
18 | }
19 |
20 | if (!password) {
21 | throw new HttpException(422, {errors: {password: ["can't be blank"]}});
22 | }
23 |
24 | await checkUserUniqueness(email, username);
25 |
26 | const hashedPassword = await bcrypt.hash(password, 10);
27 |
28 | const createdUser = await usePrisma().user.create({
29 | data: {
30 | username,
31 | email,
32 | password: hashedPassword,
33 | ...(image ? {image} : {}),
34 | ...(bio ? {bio} : {}),
35 | ...(demo ? {demo} : {}),
36 | },
37 | select: {
38 | id: true,
39 | email: true,
40 | username: true,
41 | bio: true,
42 | image: true,
43 | },
44 | });
45 |
46 | return {
47 | user: {
48 | ...createdUser,
49 | token: useGenerateToken(createdUser.id),
50 | }
51 | };
52 | });
53 |
54 | const checkUserUniqueness = async (email: string, username: string) => {
55 | const existingUserByEmail = await usePrisma().user.findUnique({
56 | where: {
57 | email,
58 | },
59 | select: {
60 | id: true,
61 | },
62 | });
63 |
64 | const existingUserByUsername = await usePrisma().user.findUnique({
65 | where: {
66 | username,
67 | },
68 | select: {
69 | id: true,
70 | },
71 | });
72 |
73 | if (existingUserByEmail || existingUserByUsername) {
74 | throw new HttpException(422, {
75 | errors: {
76 | ...(existingUserByEmail ? {email: ['has already been taken']} : {}),
77 | ...(existingUserByUsername ? {username: ['has already been taken']} : {}),
78 | },
79 | });
80 | }
81 | };
82 |
--------------------------------------------------------------------------------
/apps/documentation/src/content/docs/specifications/frontend/api.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: API
3 | ---
4 |
5 | This project provides you different solutions to test your frontend implementation with an API by:
6 |
7 | - [running our official backend implementation locally](#run-the-official-backend-implementation-locally)
8 | - [hosting your own API](#host-your-own-api)
9 | - [using the API deployed for the official demo](#demo-api)
10 |
11 | ## Run the official backend implementation locally
12 |
13 | The official backend implementation is open-sourced.
14 | You can find the GitHub repository [here](https://github.com/gothinkster/node-express-prisma-v1-official-app).
15 | The Readme will provide you guidances to start the server locally.
16 |
17 | :::info
18 | We encourage you to use this implementation's **main** branch for local tests as the **limited** one includes [limitations](#api-limitations) aimed to protect public-hosted APIs.
19 | :::
20 |
21 | ## Host your own API
22 |
23 | The official backend implementation includes a [**Deploy to Heroku** button](https://github.com/gothinkster/node-express-prisma-v1-official-app#deploy-to-heroku).
24 | This button provides you a quick and easy way to deploy the API on Heroku for your frontend implementation.
25 |
26 | :::caution
27 | The official backend implementation repository includes two branches:
28 |
29 | - the **main** branch which adheres to the RealWorld backend specs
30 | - the **limited** branch which includes limitations for public-hosted APIs
31 |
32 | The **limited** branch will be more suitable if you plan to host your implementation.
33 | [Here](#api-limitations) is the list of the limitations.
34 | :::
35 |
36 | ## Demo API
37 |
38 | This project provides you with a public hosted API to test your frontend implementations.
39 | Point your API requests to `https://api.realworld.io/api` and you're good to go!
40 |
41 | ### API Usage
42 |
43 | The API is freely available for public usage but its access is limited to RealWorld usage only: you won't be able to t consume it on its own but with a frontend application.
44 |
45 | ## API Limitations
46 |
47 | :::info
48 | To provide everyone a **safe** and **healthy** experience by not exposing free speech created content, the following limitations have been introduced in 2021
49 | :::
50 |
51 | The visibility of user content is limited :
52 |
53 | - logged out users see only content created by demo accounts
54 | - logged in users see only their content and the content created by demo accounts
55 |
--------------------------------------------------------------------------------
/apps/api/server/routes/api/articles/index.post.ts:
--------------------------------------------------------------------------------
1 | import articleMapper from "~/utils/article.mapper";
2 | import HttpException from "~/models/http-exception.model";
3 | import slugify from 'slugify';
4 | import {definePrivateEventHandler} from "~/auth-event-handler";
5 |
6 | export default definePrivateEventHandler(async (event, {auth}) => {
7 | const {article} = await readBody(event);
8 |
9 | const { title, description, body, tagList } = article;
10 | const tags = Array.isArray(tagList) ? tagList : [];
11 |
12 | if (!title) {
13 | throw new HttpException(422, { errors: { title: ["can't be blank"] } });
14 | }
15 |
16 | if (!description) {
17 | throw new HttpException(422, { errors: { description: ["can't be blank"] } });
18 | }
19 |
20 | if (!body) {
21 | throw new HttpException(422, { errors: { body: ["can't be blank"] } });
22 | }
23 |
24 | const slug = `${slugify(title)}-${auth.id}`;
25 |
26 | const existingTitle = await usePrisma().article.findUnique({
27 | where: {
28 | slug,
29 | },
30 | select: {
31 | slug: true,
32 | },
33 | });
34 |
35 | if (existingTitle) {
36 | throw new HttpException(422, { errors: { title: ['must be unique'] } });
37 | }
38 |
39 | const {
40 | authorId,
41 | id: articleId,
42 | ...createdArticle
43 | } = await usePrisma().article.create({
44 | data: {
45 | title,
46 | description,
47 | body,
48 | slug,
49 | tagList: {
50 | connectOrCreate: tags.map((tag: string) => ({
51 | create: { name: tag },
52 | where: { name: tag },
53 | })),
54 | },
55 | author: {
56 | connect: {
57 | id: auth.id,
58 | },
59 | },
60 | },
61 | include: {
62 | tagList: {
63 | select: {
64 | name: true,
65 | },
66 | },
67 | author: {
68 | select: {
69 | username: true,
70 | bio: true,
71 | image: true,
72 | followedBy: true,
73 | },
74 | },
75 | favoritedBy: true,
76 | _count: {
77 | select: {
78 | favoritedBy: true,
79 | },
80 | },
81 | },
82 | });
83 |
84 | return {article: articleMapper(createdArticle, auth.id)};
85 | });
86 |
--------------------------------------------------------------------------------
/apps/documentation/README.md:
--------------------------------------------------------------------------------
1 | # Starlight Starter Kit: Basics
2 |
3 | [](https://starlight.astro.build)
4 |
5 | ```
6 | npm create astro@latest -- --template starlight
7 | ```
8 |
9 | [](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics)
10 | [](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics)
11 | [](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics)
12 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs)
13 |
14 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
15 |
16 | ## 🚀 Project Structure
17 |
18 | Inside of your Astro + Starlight project, you'll see the following folders and files:
19 |
20 | ```
21 | .
22 | ├── public/
23 | ├── src/
24 | │ ├── assets/
25 | │ ├── content/
26 | │ │ ├── docs/
27 | │ │ └── config.ts
28 | │ └── env.d.ts
29 | ├── astro.config.mjs
30 | ├── package.json
31 | └── tsconfig.json
32 | ```
33 |
34 | Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
35 |
36 | Images can be added to `src/assets/` and embedded in Markdown with a relative link.
37 |
38 | Static assets, like favicons, can be placed in the `public/` directory.
39 |
40 | ## 🧞 Commands
41 |
42 | All commands are run from the root of the project, from a terminal:
43 |
44 | | Command | Action |
45 | | :------------------------ | :----------------------------------------------- |
46 | | `npm install` | Installs dependencies |
47 | | `npm run dev` | Starts local dev server at `localhost:4321` |
48 | | `npm run build` | Build your production site to `./dist/` |
49 | | `npm run preview` | Preview your build locally, before deploying |
50 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
51 | | `npm run astro -- --help` | Get help using the Astro CLI |
52 |
53 | ## 👀 Want to learn more?
54 |
55 | Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
56 |
--------------------------------------------------------------------------------
/apps/documentation/src/content/docs/implementation-creation/expectations.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Expectations
3 | ---
4 |
5 | ## Remember: Keep your codebases _simple_, yet _robust_.
6 |
7 | If a new developer to your framework comes along and takes longer than 10 minutes to grasp the high-level architecture, it's likely that you went a little overboard in the engineering department.
8 |
9 | Alternatively, you should _never_ forgo following fundamental best practices for the sake of simplicity, lest we teach that same newbie dev the _wrong_ way of doing things.
10 |
11 | The quality & architecture of Conduit implementations should reflect something similar to an early-stage startup's MVP: functionally complete & stable, but not unnecessarily over-engineered.
12 |
13 | ## To write tests, or to not write tests?
14 |
15 | **TL;DR** — we require a minimum of **one** unit test with every repo, but we'd definitely prefer all of them to include excellent testing coverage if the maintainers are willing to add it (or if someone in the community is kind enough to make a pull request :)
16 |
17 | We believe that tests are a good concept, and we are big supporters of TDD in general. Building Conduit implementations without complete testing coverage, on the other hand, is a significant time commitment in and of itself, therefore we didn't include it in the spec at first since we believed that if people wanted it, it would be a fantastic "extra credit" aim for the repo. For example, a request for unit tests was made in our Angular 2 repo, and several fantastic community members are presently working on a PR to address it.
18 |
19 | Another reason we didn’t include them in the spec is from the "Golden Rule" above:
20 |
21 | > The quality & architecture of Conduit implementations should reflect something similar to an early-stage startup's MVP: functionally complete & stable, but not unnecessarily over-engineered.
22 |
23 | Most startups we know that work in consumer-facing apps (like Conduit) don’t apply TDD/testing until they have a solid product-market fit, which is smart because they then spend most of their time iterating on product & UI and thus are far more likely to find PMF.
24 |
25 | This doesn’t mean that TDD/testing === over-engineering, but in certain circumstances that statement does evaluate true (ex: consumer product finding PMF, side-projects, robust prototypes, etc).
26 |
27 | That said, we do _prefer_ that every repo includes excellent tests that are exemplary of TDD/testing with that framework 👍
28 |
29 | ## Other Expectations
30 |
31 | - All the required features (see specs) should be implemented.
32 | - You should publish your implementation on a dedicated GitHub repository with the "Issues" section open.
33 | - You should provide a README that presents an overview of your implementation and explains how to run it locally.
34 | - The library/framework you are using should have at least 300 GitHub stars.
35 | - You should do your best to keep your implementation up to date.
36 |
--------------------------------------------------------------------------------
/apps/api/server/routes/api/articles/index.get.ts:
--------------------------------------------------------------------------------
1 | import articleMapper from "~/utils/article.mapper";
2 | import {definePrivateEventHandler} from "~/auth-event-handler";
3 |
4 | export default definePrivateEventHandler(async (event, {auth}) => {
5 | const query = getQuery(event);
6 |
7 | const andQueries = buildFindAllQuery(query, auth);
8 | const articlesCount = await usePrisma().article.count({
9 | where: {
10 | AND: andQueries,
11 | },
12 | });
13 |
14 | const articles = await usePrisma().article.findMany({
15 | omit: {
16 | body: true,
17 | updatedAt: true,
18 | },
19 | where: { AND: andQueries },
20 | orderBy: {
21 | createdAt: 'desc',
22 | },
23 | skip: Number(query.offset) || 0,
24 | take: Number(query.limit) || 10,
25 | include: {
26 | tagList: {
27 | select: {
28 | name: true,
29 | },
30 | },
31 | author: {
32 | select: {
33 | username: true,
34 | image: true,
35 | followedBy: true,
36 | },
37 | },
38 | favoritedBy: true,
39 | _count: {
40 | select: {
41 | favoritedBy: true,
42 | },
43 | },
44 | },
45 | });
46 |
47 | return {
48 | articles: articles.map((article: any) => articleMapper(article, auth.id)),
49 | articlesCount,
50 | };
51 | }, {requireAuth: false});
52 |
53 | const buildFindAllQuery = (query: any, auth: {id: number} | undefined) => {
54 | const queries: any = [];
55 | const orAuthorQuery = [];
56 | const andAuthorQuery = [];
57 |
58 | orAuthorQuery.push({
59 | demo: {
60 | equals: true,
61 | },
62 | });
63 |
64 | if (auth?.id) {
65 | orAuthorQuery.push({
66 | id: {
67 | equals: auth?.id,
68 | },
69 | });
70 | }
71 |
72 | if ('author' in query) {
73 | andAuthorQuery.push({
74 | username: {
75 | equals: query.author,
76 | },
77 | });
78 | }
79 |
80 | const authorQuery = {
81 | author: {
82 | OR: orAuthorQuery,
83 | AND: andAuthorQuery,
84 | },
85 | };
86 |
87 | queries.push(authorQuery);
88 |
89 | if ('tag' in query) {
90 | queries.push({
91 | tagList: {
92 | some: {
93 | name: query.tag,
94 | },
95 | },
96 | });
97 | }
98 |
99 | if ('favorited' in query) {
100 | queries.push({
101 | favoritedBy: {
102 | some: {
103 | username: {
104 | equals: query.favorited,
105 | },
106 | },
107 | },
108 | });
109 | }
110 |
111 | return queries;
112 | };
113 |
--------------------------------------------------------------------------------
/apps/api/server/routes/api/articles/[slug]/index.put.ts:
--------------------------------------------------------------------------------
1 | import HttpException from "~/models/http-exception.model";
2 | import articleMapper from "~/utils/article.mapper";
3 | import slugify from 'slugify';
4 | import {definePrivateEventHandler} from "~/auth-event-handler";
5 |
6 | export default definePrivateEventHandler(async (event, {auth}) => {
7 | const {article} = await readBody(event);
8 | const slug = getRouterParam(event, 'slug');
9 |
10 | let newSlug = null;
11 |
12 | const existingArticle = await usePrisma().article.findFirst({
13 | where: {
14 | slug,
15 | },
16 | select: {
17 | author: {
18 | select: {
19 | id: true,
20 | username: true,
21 | },
22 | },
23 | },
24 | });
25 |
26 | if (!existingArticle) {
27 | throw new HttpException(404, {});
28 | }
29 |
30 | if (existingArticle.author.id !== auth.id) {
31 | throw new HttpException(403, {
32 | message: 'You are not authorized to update this article',
33 | });
34 | }
35 |
36 | if (article.title) {
37 | newSlug = `${slugify(article.title)}-${auth.id}`;
38 |
39 | if (newSlug !== slug) {
40 | const existingTitle = await usePrisma().article.findFirst({
41 | where: {
42 | slug: newSlug,
43 | },
44 | select: {
45 | slug: true,
46 | },
47 | });
48 |
49 | if (existingTitle) {
50 | throw new HttpException(422, { errors: { title: ['must be unique'] } });
51 | }
52 | }
53 | }
54 |
55 | const tagList =
56 | Array.isArray(article.tagList) && article.tagList?.length
57 | ? article.tagList.map((tag: string) => ({
58 | create: { name: tag },
59 | where: { name: tag },
60 | }))
61 | : [];
62 |
63 | await disconnectArticlesTags(slug);
64 |
65 | const updatedArticle = await usePrisma().article.update({
66 | where: {
67 | slug,
68 | },
69 | data: {
70 | ...(article.title ? { title: article.title } : {}),
71 | ...(article.body ? { body: article.body } : {}),
72 | ...(article.description ? { description: article.description } : {}),
73 | ...(newSlug ? { slug: newSlug } : {}),
74 | updatedAt: new Date(),
75 | tagList: {
76 | connectOrCreate: tagList,
77 | },
78 | },
79 | include: {
80 | tagList: {
81 | select: {
82 | name: true,
83 | },
84 | },
85 | author: {
86 | select: {
87 | username: true,
88 | bio: true,
89 | image: true,
90 | followedBy: true,
91 | },
92 | },
93 | favoritedBy: true,
94 | _count: {
95 | select: {
96 | favoritedBy: true,
97 | },
98 | },
99 | },
100 | });
101 |
102 | return {article: articleMapper(updatedArticle, auth.id)};
103 | });
104 |
105 | const disconnectArticlesTags = async (slug: string) => {
106 | await usePrisma().article.update({
107 | where: {
108 | slug,
109 | },
110 | data: {
111 | tagList: {
112 | set: [],
113 | },
114 | },
115 | });
116 | };
117 |
--------------------------------------------------------------------------------
/apps/api/prisma/migrations/20241009081140_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Article" (
3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
4 | "slug" TEXT NOT NULL,
5 | "title" TEXT NOT NULL,
6 | "description" TEXT NOT NULL,
7 | "body" TEXT NOT NULL,
8 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
9 | "updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
10 | "authorId" INTEGER NOT NULL,
11 | CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
12 | );
13 |
14 | -- CreateTable
15 | CREATE TABLE "Comment" (
16 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
17 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
18 | "updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
19 | "body" TEXT NOT NULL,
20 | "articleId" INTEGER NOT NULL,
21 | "authorId" INTEGER NOT NULL,
22 | CONSTRAINT "Comment_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
23 | CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
24 | );
25 |
26 | -- CreateTable
27 | CREATE TABLE "Tag" (
28 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
29 | "name" TEXT NOT NULL
30 | );
31 |
32 | -- CreateTable
33 | CREATE TABLE "User" (
34 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
35 | "email" TEXT NOT NULL,
36 | "username" TEXT NOT NULL,
37 | "password" TEXT NOT NULL,
38 | "image" TEXT DEFAULT 'https://api.realworld.io/images/smiley-cyrus.jpeg',
39 | "bio" TEXT,
40 | "demo" BOOLEAN NOT NULL DEFAULT false
41 | );
42 |
43 | -- CreateTable
44 | CREATE TABLE "_ArticleToTag" (
45 | "A" INTEGER NOT NULL,
46 | "B" INTEGER NOT NULL,
47 | CONSTRAINT "_ArticleToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Article" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
48 | CONSTRAINT "_ArticleToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag" ("id") ON DELETE CASCADE ON UPDATE CASCADE
49 | );
50 |
51 | -- CreateTable
52 | CREATE TABLE "_UserFavorites" (
53 | "A" INTEGER NOT NULL,
54 | "B" INTEGER NOT NULL,
55 | CONSTRAINT "_UserFavorites_A_fkey" FOREIGN KEY ("A") REFERENCES "Article" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
56 | CONSTRAINT "_UserFavorites_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
57 | );
58 |
59 | -- CreateTable
60 | CREATE TABLE "_UserFollows" (
61 | "A" INTEGER NOT NULL,
62 | "B" INTEGER NOT NULL,
63 | CONSTRAINT "_UserFollows_A_fkey" FOREIGN KEY ("A") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
64 | CONSTRAINT "_UserFollows_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
65 | );
66 |
67 | -- CreateIndex
68 | CREATE UNIQUE INDEX "Article_slug_key" ON "Article"("slug");
69 |
70 | -- CreateIndex
71 | CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
72 |
73 | -- CreateIndex
74 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
75 |
76 | -- CreateIndex
77 | CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
78 |
79 | -- CreateIndex
80 | CREATE UNIQUE INDEX "_ArticleToTag_AB_unique" ON "_ArticleToTag"("A", "B");
81 |
82 | -- CreateIndex
83 | CREATE INDEX "_ArticleToTag_B_index" ON "_ArticleToTag"("B");
84 |
85 | -- CreateIndex
86 | CREATE UNIQUE INDEX "_UserFavorites_AB_unique" ON "_UserFavorites"("A", "B");
87 |
88 | -- CreateIndex
89 | CREATE INDEX "_UserFavorites_B_index" ON "_UserFavorites"("B");
90 |
91 | -- CreateIndex
92 | CREATE UNIQUE INDEX "_UserFollows_AB_unique" ON "_UserFollows"("A", "B");
93 |
94 | -- CreateIndex
95 | CREATE INDEX "_UserFollows_B_index" ON "_UserFollows"("B");
96 |
--------------------------------------------------------------------------------
/apps/documentation/src/content/docs/community/authors.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Authors
3 | ---
4 |
5 | # Who currently maintain the project
6 |
7 | #### [Gérôme Grignon](https://github.com/geromegrignon) - Maintainer
8 |
9 |