<%= get_flash(@conn, :info) %>
3 |<%= get_flash(@conn, :error) %>
4 | <%= @inner_content %> 5 |├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README_FA.md ├── SECURITY.md ├── apps ├── mishka_api │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── mishka_api.ex │ │ ├── mishka_api │ │ │ ├── application.ex │ │ │ ├── plug │ │ │ │ ├── access_token_plug.ex │ │ │ │ ├── acl_check_plug.ex │ │ │ │ └── user_limiter_plug.ex │ │ │ └── protocols │ │ │ │ ├── auth_protocol.ex │ │ │ │ └── content_protocol.ex │ │ ├── mishka_api_web.ex │ │ └── mishka_api_web │ │ │ ├── channels │ │ │ └── user_socket.ex │ │ │ ├── controllers │ │ │ ├── auth_controller.ex │ │ │ └── content_controller.ex │ │ │ ├── endpoint.ex │ │ │ ├── gettext.ex │ │ │ ├── router.ex │ │ │ ├── telemetry.ex │ │ │ └── views │ │ │ ├── error_helpers.ex │ │ │ └── error_view.ex │ ├── mix.exs │ └── test │ │ ├── mishka_api_web │ │ ├── controllers │ │ │ ├── auth_controller_test.exs │ │ │ └── content_contoller_test.exs │ │ └── views │ │ │ └── error_view_test.exs │ │ ├── support │ │ ├── channel_case.ex │ │ └── conn_case.ex │ │ └── test_helper.exs ├── mishka_content │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── blog │ │ │ ├── author.ex │ │ │ ├── category.ex │ │ │ ├── like.ex │ │ │ ├── link.ex │ │ │ ├── post.ex │ │ │ ├── tag.ex │ │ │ └── tag_mapper.ex │ │ ├── cache │ │ │ ├── bookmark_dynamic_supervisor.ex │ │ │ ├── bookmark_management.ex │ │ │ ├── content_draft_dynamic_supervisor.ex │ │ │ └── content_draft_management.ex │ │ ├── core_plugins │ │ │ ├── register │ │ │ │ └── success_register.ex │ │ │ └── user │ │ │ │ ├── login │ │ │ │ ├── success_login.ex │ │ │ │ └── success_logout.ex │ │ │ │ └── role │ │ │ │ └── success_add_role.ex │ │ ├── email │ │ │ ├── email.ex │ │ │ ├── email_helper.ex │ │ │ └── mailer.ex │ │ ├── general │ │ │ ├── activity.ex │ │ │ ├── bookmark.ex │ │ │ ├── comment.ex │ │ │ ├── comment_like.ex │ │ │ ├── notif.ex │ │ │ ├── subscription.ex │ │ │ └── user_notif_status.ex │ │ ├── mishka_content.ex │ │ ├── mishka_content │ │ │ └── application.ex │ │ └── social │ │ │ ├── facebook.ex │ │ │ ├── sender.ex │ │ │ └── twitter.ex │ ├── mix.exs │ └── test │ │ ├── activity_test.exs │ │ ├── blog │ │ ├── blog_author_test.exs │ │ ├── blog_link_test.exs │ │ ├── blog_tag_test.exs │ │ ├── category_test.exs │ │ ├── post_like_test.exs │ │ └── post_test.exs │ │ ├── bookmark_test.exs │ │ ├── comment_like_test.exs │ │ ├── comment_test.exs │ │ ├── mishka_content_test.exs │ │ ├── notif_test.exs │ │ ├── subscription_test.exs │ │ └── test_helper.exs ├── mishka_database │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── mishka_database.ex │ │ ├── mishka_database │ │ │ ├── application.ex │ │ │ └── repo.ex │ │ └── schema │ │ │ ├── mishka_content │ │ │ ├── activity.ex │ │ │ ├── blog_author.ex │ │ │ ├── blog_like.ex │ │ │ ├── blog_link.ex │ │ │ ├── blog_tag.ex │ │ │ ├── blog_tag_mapper.ex │ │ │ ├── bookmark.ex │ │ │ ├── category.ex │ │ │ ├── comment.ex │ │ │ ├── comment_like.ex │ │ │ ├── content_schema_enum.ex │ │ │ ├── notif.ex │ │ │ ├── post.ex │ │ │ ├── post_like.ex │ │ │ ├── subscription.ex │ │ │ └── user_notif_status.ex │ │ │ └── mishka_user │ │ │ ├── identity_provider.ex │ │ │ ├── permission.ex │ │ │ ├── role.ex │ │ │ ├── user.ex │ │ │ ├── user_role.ex │ │ │ ├── user_schema_enum.ex │ │ │ └── user_token.ex │ ├── mix.exs │ ├── priv │ │ └── repo │ │ │ ├── migrations │ │ │ ├── 20210111193110_users.exs │ │ │ ├── 20210328064612_identities.exs │ │ │ ├── 20210328103652_roles.exs │ │ │ ├── 20210328103704_permissions.exs │ │ │ ├── 20210329064603_blog_categories.exs │ │ │ ├── 20210329064625_blog_posts.exs │ │ │ ├── 20210330072832_blog_tags.exs │ │ │ ├── 20210330072837_blog_tags_mappers.exs │ │ │ ├── 20210330132342_blog_authors.exs │ │ │ ├── 20210330132407_blog_likes.exs │ │ │ ├── 20210330141536_blog_links.exs │ │ │ ├── 20210330142326_subscriptions.exs │ │ │ ├── 20210330142350_bookmarks.exs │ │ │ ├── 20210330142430_notifs.exs │ │ │ ├── 20210330162305_comments.exs │ │ │ ├── 20210330224243_comments_likes.exs │ │ │ ├── 20210402080613_users_roles.exs │ │ │ ├── 20211005180905_user_notif_statuses.exs │ │ │ └── 20220626144233_user_tokens.exs │ │ │ └── seeds.exs │ └── test │ │ ├── helpers │ │ └── mishka_database_crud_macro_test.exs │ │ └── test_helper.exs ├── mishka_file │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── mishka_file.ex │ │ └── mishka_file │ │ │ └── application.ex │ ├── mix.exs │ └── test │ │ ├── mishka_file_test.exs │ │ └── test_helper.exs ├── mishka_html │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── assets │ │ ├── css │ │ │ ├── app.css │ │ │ └── phoenix.css │ │ ├── js │ │ │ └── app.js │ │ ├── static │ │ │ ├── css │ │ │ │ ├── bootstrap.min.css │ │ │ │ ├── bootstrap.min.css.map │ │ │ │ ├── ckeditor-styles.css │ │ │ │ ├── default-template.css │ │ │ │ ├── font.css │ │ │ │ └── main.min.css │ │ │ ├── favicon.ico │ │ │ ├── fonts │ │ │ │ ├── Iconly-bulk.eot │ │ │ │ ├── Iconly-bulk.svg │ │ │ │ ├── Iconly-bulk.ttf │ │ │ │ ├── Iconly-bulk.woff │ │ │ │ ├── Iconly-light.eot │ │ │ │ ├── Vazir-Regular.eot │ │ │ │ ├── Vazir-Regular.ttf │ │ │ │ ├── Vazir-Regular.woff │ │ │ │ └── Vazir-Regular.woff2 │ │ │ ├── images │ │ │ │ ├── 1.jpg │ │ │ │ ├── 2.jpg │ │ │ │ ├── 3.jpg │ │ │ │ ├── 4.jpg │ │ │ │ ├── 5.jpg │ │ │ │ ├── icons8-login-as-user-80.png │ │ │ │ ├── mylogo.png │ │ │ │ └── no-user-image.jpg │ │ │ ├── js │ │ │ │ ├── bootstrap.bundle.min.js │ │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ │ ├── bootstrap.min.js │ │ │ │ ├── bootstrap.min.js.map │ │ │ │ ├── check_editor_extended.js │ │ │ │ ├── ckeditor.js │ │ │ │ ├── ckeditor.js.map │ │ │ │ ├── main.min.js │ │ │ │ └── translations │ │ │ │ │ ├── af.js │ │ │ │ │ ├── ar.js │ │ │ │ │ ├── ast.js │ │ │ │ │ ├── az.js │ │ │ │ │ ├── bg.js │ │ │ │ │ ├── ca.js │ │ │ │ │ ├── cs.js │ │ │ │ │ ├── da.js │ │ │ │ │ ├── de-ch.js │ │ │ │ │ ├── de.js │ │ │ │ │ ├── el.js │ │ │ │ │ ├── en-au.js │ │ │ │ │ ├── en-gb.js │ │ │ │ │ ├── eo.js │ │ │ │ │ ├── es.js │ │ │ │ │ ├── et.js │ │ │ │ │ ├── eu.js │ │ │ │ │ ├── fa.js │ │ │ │ │ ├── fi.js │ │ │ │ │ ├── fr.js │ │ │ │ │ ├── gl.js │ │ │ │ │ ├── gu.js │ │ │ │ │ ├── he.js │ │ │ │ │ ├── hi.js │ │ │ │ │ ├── hr.js │ │ │ │ │ ├── hu.js │ │ │ │ │ ├── id.js │ │ │ │ │ ├── it.js │ │ │ │ │ ├── ja.js │ │ │ │ │ ├── kk.js │ │ │ │ │ ├── km.js │ │ │ │ │ ├── kn.js │ │ │ │ │ ├── ko.js │ │ │ │ │ ├── ku.js │ │ │ │ │ ├── lt.js │ │ │ │ │ ├── lv.js │ │ │ │ │ ├── nb.js │ │ │ │ │ ├── ne.js │ │ │ │ │ ├── nl.js │ │ │ │ │ ├── no.js │ │ │ │ │ ├── oc.js │ │ │ │ │ ├── pl.js │ │ │ │ │ ├── pt-br.js │ │ │ │ │ ├── pt.js │ │ │ │ │ ├── ro.js │ │ │ │ │ ├── ru.js │ │ │ │ │ ├── si.js │ │ │ │ │ ├── sk.js │ │ │ │ │ ├── sl.js │ │ │ │ │ ├── sq.js │ │ │ │ │ ├── sr-latn.js │ │ │ │ │ ├── sr.js │ │ │ │ │ ├── sv.js │ │ │ │ │ ├── th.js │ │ │ │ │ ├── tk.js │ │ │ │ │ ├── tr.js │ │ │ │ │ ├── tt.js │ │ │ │ │ ├── ug.js │ │ │ │ │ ├── uk.js │ │ │ │ │ ├── vi.js │ │ │ │ │ ├── zh-cn.js │ │ │ │ │ └── zh.js │ │ │ └── robots.txt │ │ └── vendor │ │ │ └── topbar.js │ ├── lib │ │ ├── mishka_html.ex │ │ ├── mishka_html │ │ │ ├── application.ex │ │ │ ├── helpers │ │ │ │ └── live_crud.ex │ │ │ └── plug │ │ │ │ ├── acl_check_plug.ex │ │ │ │ ├── current_token_plug.ex │ │ │ │ ├── live_acl_check_plug.ex │ │ │ │ └── not_login_plug.ex │ │ ├── mishka_html_web.ex │ │ └── mishka_html_web │ │ │ ├── channels │ │ │ └── user_socket.ex │ │ │ ├── controllers │ │ │ └── auth_controller.ex │ │ │ ├── endpoint.ex │ │ │ ├── gettext.ex │ │ │ ├── live │ │ │ ├── admin_activities_live.ex │ │ │ ├── admin_activity_live.ex │ │ │ ├── admin_blog_categories_live.ex │ │ │ ├── admin_blog_category_live.ex │ │ │ ├── admin_blog_post_authors_live.ex │ │ │ ├── admin_blog_post_live.ex │ │ │ ├── admin_blog_post_tags_live.ex │ │ │ ├── admin_blog_posts_live.ex │ │ │ ├── admin_blog_tag_live.ex │ │ │ ├── admin_blog_tags_live.ex │ │ │ ├── admin_comment_live.ex │ │ │ ├── admin_comments_live.ex │ │ │ ├── admin_dashboard_live.ex │ │ │ ├── admin_link_live.ex │ │ │ ├── admin_links_live.ex │ │ │ ├── admin_media_manager_live.ex │ │ │ ├── admin_notif.ex │ │ │ ├── admin_notifs.ex │ │ │ ├── admin_seo_live.ex │ │ │ ├── admin_setting_live.ex │ │ │ ├── admin_settings_live.ex │ │ │ ├── admin_subscription_live.ex │ │ │ ├── admin_subscriptions_live.ex │ │ │ ├── admin_user_live.ex │ │ │ ├── admin_user_role_live.ex │ │ │ ├── admin_user_role_permissions_live.ex │ │ │ ├── admin_user_roles_live.ex │ │ │ ├── admin_users_live.ex │ │ │ ├── blog_category_live.ex │ │ │ ├── blog_post_live.ex │ │ │ ├── blogs_live.ex │ │ │ ├── bookmarks_live.ex │ │ │ ├── components │ │ │ │ ├── admin │ │ │ │ │ ├── dashboard │ │ │ │ │ │ ├── admin_dashboard_activities.ex │ │ │ │ │ │ ├── admin_dashboard_last_users_component.ex │ │ │ │ │ │ ├── admin_dashboard_quick_menu.ex │ │ │ │ │ │ ├── admin_last_blog_posts_component.ex │ │ │ │ │ │ └── admin_last_notif_component.ex │ │ │ │ │ ├── form │ │ │ │ │ │ ├── add_field_component.ex │ │ │ │ │ │ ├── add_tag_component.ex │ │ │ │ │ │ ├── converte_title_to_link_component.ex │ │ │ │ │ │ ├── editor_component.ex │ │ │ │ │ │ ├── select_component.ex │ │ │ │ │ │ ├── text_component.ex │ │ │ │ │ │ ├── text_search_component.ex │ │ │ │ │ │ ├── textarea_component.ex │ │ │ │ │ │ └── upload_component.ex │ │ │ │ │ ├── public │ │ │ │ │ │ ├── _admin_menu.ex │ │ │ │ │ │ ├── _live_flash_component.ex │ │ │ │ │ │ ├── _modal_component.ex │ │ │ │ │ │ ├── _notif.ex │ │ │ │ │ │ ├── admin_calendar_component.ex │ │ │ │ │ │ └── draft_block_component.ex │ │ │ │ │ └── setting │ │ │ │ │ │ └── add_field_component.ex │ │ │ │ ├── client │ │ │ │ │ ├── blog_post │ │ │ │ │ │ └── sub_comment.ex │ │ │ │ │ ├── home │ │ │ │ │ │ ├── banner_block_component.ex │ │ │ │ │ │ └── normal_block_component.ex │ │ │ │ │ ├── login │ │ │ │ │ │ └── login_form_component.ex │ │ │ │ │ ├── public │ │ │ │ │ │ ├── _client_header_component.ex │ │ │ │ │ │ ├── _client_menu_and_notif.ex │ │ │ │ │ │ └── _cright_component.ex │ │ │ │ │ └── register │ │ │ │ │ │ └── register_form_component.ex │ │ │ │ └── public │ │ │ │ │ ├── _pagination_component.ex │ │ │ │ │ ├── _time_converter_component.ex │ │ │ │ │ ├── activities_component.ex │ │ │ │ │ ├── flash_component.ex │ │ │ │ │ ├── list_container_component.ex │ │ │ │ │ ├── list_item_component.ex │ │ │ │ │ ├── notif.ex │ │ │ │ │ ├── search_component.ex │ │ │ │ │ └── time_converter_component.ex │ │ │ ├── home_live.ex │ │ │ ├── login_live.ex │ │ │ ├── notif_live.ex │ │ │ ├── notifs_live.ex │ │ │ ├── register_live.ex │ │ │ ├── reset_change_password_live.ex │ │ │ └── reset_password_live.ex │ │ │ ├── router.ex │ │ │ ├── telemetry.ex │ │ │ ├── templates │ │ │ ├── admin_activity │ │ │ │ ├── admin_activities_live.html.heex │ │ │ │ └── admin_activity_live.html.heex │ │ │ ├── admin_blog │ │ │ │ ├── admin_blog_category_live.html.heex │ │ │ │ ├── admin_blog_post_live.html.heex │ │ │ │ ├── admin_blog_tag_live.html.heex │ │ │ │ └── admin_link_live.html.heex │ │ │ ├── admin_comment │ │ │ │ └── admin_comment_live.html.heex │ │ │ ├── admin_dashboard │ │ │ │ └── admin_dashboard_live.html.heex │ │ │ ├── admin_media_manager │ │ │ │ └── admin_media_manager_live.html.heex │ │ │ ├── admin_notif │ │ │ │ ├── admin_notif_live.html.heex │ │ │ │ └── admin_notif_show_live.html.heex │ │ │ ├── admin_seo │ │ │ │ └── admin_seo_live.html.heex │ │ │ ├── admin_setting │ │ │ │ └── admin_setting_live.html.heex │ │ │ ├── admin_subscription │ │ │ │ └── admin_subscription_live.html.heex │ │ │ ├── admin_user │ │ │ │ ├── admin_user_live.html.heex │ │ │ │ └── admin_user_role_live.html.heex │ │ │ ├── client_auth │ │ │ │ ├── login_live.html.heex │ │ │ │ ├── register_live.html.heex │ │ │ │ ├── reset_change_password_live.html.heex │ │ │ │ └── reset_password_live.html.heex │ │ │ ├── client_blog │ │ │ │ ├── blog_category_live.html.heex │ │ │ │ ├── blog_post_live.html.heex │ │ │ │ ├── blogs_live.html.heex │ │ │ │ └── bookmarks_live.html.heex │ │ │ ├── client_home │ │ │ │ └── home_live.html.heex │ │ │ ├── client_notif │ │ │ │ ├── notif_live.html.heex │ │ │ │ └── notifs_live.html.heex │ │ │ └── layout │ │ │ │ ├── app.html.heex │ │ │ │ ├── live.html.heex │ │ │ │ └── root.html.heex │ │ │ └── views │ │ │ ├── admin_activity_view.ex │ │ │ ├── admin_blog_view.ex │ │ │ ├── admin_comment_view.ex │ │ │ ├── admin_dashboard_view.ex │ │ │ ├── admin_media_manager_view.ex │ │ │ ├── admin_notif_view.ex │ │ │ ├── admin_seo_view.ex │ │ │ ├── admin_setting_view.ex │ │ │ ├── admin_subscription_view.ex │ │ │ ├── admin_user_view.ex │ │ │ ├── client_auth_view.ex │ │ │ ├── client_blog_view.ex │ │ │ ├── client_home_view.ex │ │ │ ├── client_notif_view.ex │ │ │ ├── error_helpers.ex │ │ │ ├── error_view.ex │ │ │ └── layout_view.ex │ ├── mix.exs │ ├── priv │ │ └── static │ │ │ ├── cache_manifest.json │ │ │ ├── css │ │ │ ├── bootstrap-icons.css │ │ │ └── font.css │ │ │ ├── favicon.ico │ │ │ ├── fonts │ │ │ ├── Iconly-bulk.eot │ │ │ ├── Iconly-bulk.svg │ │ │ ├── Iconly-bulk.ttf │ │ │ ├── Iconly-bulk.woff │ │ │ ├── Iconly-light.eot │ │ │ ├── Vazir-Regular.eot │ │ │ ├── Vazir-Regular.ttf │ │ │ ├── Vazir-Regular.woff │ │ │ ├── Vazir-Regular.woff2 │ │ │ ├── bootstrap-icons.woff │ │ │ ├── bootstrap-icons.woff2 │ │ │ └── font.css │ │ │ ├── images │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── icons8-login-as-user-80.png │ │ │ ├── mylogo.png │ │ │ └── no-user-image.jpg │ │ │ ├── js │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.min.js │ │ │ ├── bootstrap.min.js.map │ │ │ ├── check_editor_extended.js │ │ │ ├── ckeditor.js │ │ │ ├── ckeditor.js.map │ │ │ ├── main.min.js │ │ │ └── translations │ │ │ │ ├── af.js │ │ │ │ ├── ar.js │ │ │ │ ├── ast.js │ │ │ │ ├── az.js │ │ │ │ ├── bg.js │ │ │ │ ├── ca.js │ │ │ │ ├── cs.js │ │ │ │ ├── da.js │ │ │ │ ├── de-ch.js │ │ │ │ ├── de.js │ │ │ │ ├── el.js │ │ │ │ ├── en-au.js │ │ │ │ ├── en-gb.js │ │ │ │ ├── eo.js │ │ │ │ ├── es.js │ │ │ │ ├── et.js │ │ │ │ ├── eu.js │ │ │ │ ├── fa.js │ │ │ │ ├── fi.js │ │ │ │ ├── fr.js │ │ │ │ ├── gl.js │ │ │ │ ├── gu.js │ │ │ │ ├── he.js │ │ │ │ ├── hi.js │ │ │ │ ├── hr.js │ │ │ │ ├── hu.js │ │ │ │ ├── id.js │ │ │ │ ├── it.js │ │ │ │ ├── ja.js │ │ │ │ ├── kk.js │ │ │ │ ├── km.js │ │ │ │ ├── kn.js │ │ │ │ ├── ko.js │ │ │ │ ├── ku.js │ │ │ │ ├── lt.js │ │ │ │ ├── lv.js │ │ │ │ ├── nb.js │ │ │ │ ├── ne.js │ │ │ │ ├── nl.js │ │ │ │ ├── no.js │ │ │ │ ├── oc.js │ │ │ │ ├── pl.js │ │ │ │ ├── pt-br.js │ │ │ │ ├── pt.js │ │ │ │ ├── ro.js │ │ │ │ ├── ru.js │ │ │ │ ├── si.js │ │ │ │ ├── sk.js │ │ │ │ ├── sl.js │ │ │ │ ├── sq.js │ │ │ │ ├── sr-latn.js │ │ │ │ ├── sr.js │ │ │ │ ├── sv.js │ │ │ │ ├── th.js │ │ │ │ ├── tk.js │ │ │ │ ├── tr.js │ │ │ │ ├── tt.js │ │ │ │ ├── ug.js │ │ │ │ ├── uk.js │ │ │ │ ├── vi.js │ │ │ │ ├── zh-cn.js │ │ │ │ └── zh.js │ │ │ └── robots.txt │ └── test │ │ ├── mishka_html_web │ │ ├── live │ │ │ └── page_live_test.exs │ │ └── views │ │ │ ├── error_view_test.exs │ │ │ └── layout_view_test.exs │ │ ├── support │ │ ├── channel_case.ex │ │ └── conn_case.ex │ │ └── test_helper.exs ├── mishka_translator │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── gettext.ex │ │ ├── mishka_translator.ex │ │ └── mishka_translator │ │ │ └── application.ex │ ├── mix.exs │ ├── priv │ │ └── gettext │ │ │ ├── api_auth.pot │ │ │ ├── api_content.pot │ │ │ ├── content_email.pot │ │ │ ├── db_schema_content.pot │ │ │ ├── db_schema_public.pot │ │ │ ├── db_schema_user.pot │ │ │ ├── default.pot │ │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ ├── api_auth.po │ │ │ │ ├── api_content.po │ │ │ │ ├── content_email.po │ │ │ │ ├── db_schema_content.po │ │ │ │ ├── db_schema_public.po │ │ │ │ ├── db_schema_user.po │ │ │ │ ├── default.po │ │ │ │ ├── errors.po │ │ │ │ ├── html_auth.po │ │ │ │ ├── html_live.po │ │ │ │ ├── html_live_component.po │ │ │ │ ├── html_live_templates.po │ │ │ │ ├── macro_live.po │ │ │ │ ├── user_captcha.po │ │ │ │ └── users.po │ │ │ ├── errors.pot │ │ │ ├── fa │ │ │ └── LC_MESSAGES │ │ │ │ ├── api_auth.po │ │ │ │ ├── api_content.po │ │ │ │ ├── content_email.po │ │ │ │ ├── db_schema_content.po │ │ │ │ ├── db_schema_public.po │ │ │ │ ├── db_schema_user.po │ │ │ │ ├── default.po │ │ │ │ ├── errors.po │ │ │ │ ├── html_auth.po │ │ │ │ ├── html_live.po │ │ │ │ ├── html_live_component.po │ │ │ │ ├── html_live_templates.po │ │ │ │ ├── macro_live.po │ │ │ │ ├── user_captcha.po │ │ │ │ └── users.po │ │ │ ├── html_auth.pot │ │ │ ├── html_live.pot │ │ │ ├── html_live_component.pot │ │ │ ├── html_live_templates.pot │ │ │ ├── macro_live.pot │ │ │ ├── user_captcha.pot │ │ │ └── users.pot │ └── test │ │ ├── mishka_translator_test.exs │ │ └── test_helper.exs └── mishka_user │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ ├── acl │ │ ├── access.ex │ │ ├── acl_management.ex │ │ ├── action.ex │ │ ├── permission.ex │ │ ├── role.ex │ │ └── user_role.ex │ ├── core_plugins │ │ └── login │ │ │ ├── success_login.ex │ │ │ └── success_logout.ex │ ├── guardian │ │ ├── auth_error_handler.ex │ │ ├── auth_pipeline.ex │ │ └── guardian.ex │ ├── identity.ex │ ├── mishka_user.ex │ ├── mishka_user │ │ └── application.ex │ ├── token │ │ ├── current_phoenix_token.ex │ │ ├── jwt_token.ex │ │ ├── phoenix_token.ex │ │ ├── token.ex │ │ ├── token_managemnt.ex │ │ └── user_token.ex │ ├── user.ex │ ├── validation │ │ ├── google_recaptcha.ex │ │ ├── random_code.ex │ │ └── user_input.ex │ └── worker │ │ ├── expire_random_code_worker.ex │ │ └── expire_token_worker.ex │ ├── mix.exs │ └── test │ ├── acl │ ├── permission_test.exs │ ├── role_test.exs │ └── user_role_test.exs │ ├── mishka_user_test.exs │ ├── test_helper.exs │ └── token │ └── token_test.exs ├── config ├── config.exs ├── dev.exs ├── prod.exs ├── prod.secret.exs └── test.exs ├── deployment └── docker │ ├── bin │ ├── onefetch_linux │ └── onefetch_macos │ ├── dockers │ ├── Dockerfile │ ├── docker-compose_dev_with_nginx.yml │ ├── docker-compose_dev_without_nginx.yml │ ├── docker-compose_with_nginx.yml │ ├── docker-compose_without_nginx.yml │ └── entrypoint.sh │ ├── docs │ └── mishka-logo.ans │ ├── etc │ ├── nginx │ │ └── conf │ │ │ ├── conf.d │ │ │ ├── default.conf │ │ │ ├── mishka_api.conf │ │ │ └── mishka_cms.conf │ │ │ ├── fastcgi.conf │ │ │ ├── fastcgi_params │ │ │ ├── mime.types │ │ │ ├── modules │ │ │ ├── nginx.conf │ │ │ ├── sample_conf │ │ │ ├── mishka_api.conf │ │ │ ├── mishka_cms.conf │ │ │ ├── ssl_dev.conf │ │ │ └── ssl_prod.conf │ │ │ ├── scgi_params │ │ │ ├── ssl.conf │ │ │ └── uwsgi_params │ └── ssl │ │ ├── dev │ │ ├── rootCA_example.pem │ │ ├── server_example.crt │ │ └── server_example.key │ │ └── letsencrypt │ │ ├── dhparam2048.pem │ │ └── lets-encrypt-x3-cross-signed.pem │ ├── mishka.sh │ └── src │ ├── functions.sh │ └── variables.sh ├── integration_test └── postman │ └── v1 │ └── mishkacms-api.json └── mix.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["mix.exs", "config/*.exs"], 3 | subdirectories: ["apps/*"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | 23 | # General 24 | .DS_Store 25 | Desktop.ini 26 | .AppleDouble 27 | .LSOverride 28 | 29 | 30 | # Icon must end with two \r 31 | Icon 32 | Thumbs.db 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear in the root of a volume 37 | .DocumentRevisions-V100 38 | .fseventsd 39 | .Spotlight-V100 40 | .TemporaryItems 41 | .Trashes 42 | .VolumeIcon.icns 43 | .com.apple.timemachine.donotpresent 44 | 45 | # Directories potentially created on remote AFP share 46 | .AppleDB 47 | .AppleDesktop 48 | Network Trash Folder 49 | Temporary Items 50 | .apdisk 51 | 52 | # Compiled Python files 53 | *.pyc 54 | 55 | # Compiled C++ files 56 | *.out 57 | 58 | Dockerfile_postgres 59 | init_db.sh 60 | 61 | mix.lock 62 | 63 | 64 | # Migration 65 | apps/mishka_database/priv/repo/migrations/20220622100108_setting_migration.exs 66 | apps/mishka_database/priv/repo/migrations/20220622100110_plugin_migration.exs 67 | apps/mishka_database/priv/repo/migrations/20220622100112_oban_migration.exs 68 | apps/mishka_database/priv/repo/migrations/20220622100114_dependency_migration.exs 69 | apps/mishka_database/priv/repo/migrations/20220622100116_activity_migration.exs 70 | 71 | # Prevent test folder 72 | **/deployment/extensions 73 | .elixir_ls -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | start_shell: 2 | cd deployment/docker; chmod +x mishka.sh; sudo ./mishka.sh; 3 | 4 | start_elixir: 5 | mix deps.get 6 | mix deps.compile 7 | mix ecto.create 8 | cd apps/mishka_database; mix mishka_installer.db.gen.migration || true 9 | mix ecto.migrate 10 | mix assets.deploy || true 11 | mix run apps/mishka_database/priv/repo/seeds.exs 12 | iex -S mix phx.server 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | MishkaCms applies bug fixes only to the latest minor branch. Security patches are available for the last 2 minor branches: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.0.2 | Bug fixes and security patches | 10 | | 0.0.1 | Has been stopped| 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Please disclose security vulnerabilities privately at shahryar.tbiz@gmail.com 15 | -------------------------------------------------------------------------------- /apps/mishka_api/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/mishka_api/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | mishka_api-*.tar 24 | 25 | # Since we are building assets from assets/, 26 | # we ignore priv/static. You may want to comment 27 | # this depending on your deployment strategy. 28 | /priv/static/ 29 | -------------------------------------------------------------------------------- /apps/mishka_api/README.md: -------------------------------------------------------------------------------- 1 | # MishkaApi 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Start Phoenix endpoint with `mix phx.server` 7 | 8 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 9 | 10 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 11 | 12 | ## Learn more 13 | 14 | * Official website: https://www.phoenixframework.org/ 15 | * Guides: https://hexdocs.pm/phoenix/overview.html 16 | * Docs: https://hexdocs.pm/phoenix 17 | * Forum: https://elixirforum.com/c/phoenix-forum 18 | * Source: https://github.com/phoenixframework/phoenix 19 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApi do 2 | @moduledoc """ 3 | MishkaApi keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | 10 | def get_config(item) do 11 | :mishka_api 12 | |> Application.fetch_env!(:auth) 13 | |> Keyword.fetch!(item) 14 | end 15 | 16 | def cowboy_ip(conn) do 17 | to_string(:inet_parse.ntoa(conn.remote_ip)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api/application.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApi.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | children = [ 10 | # Start the Telemetry supervisor 11 | MishkaApiWeb.Telemetry, 12 | # Start the PubSub system 13 | {Phoenix.PubSub, name: MishkaApi.PubSub}, 14 | # Start the Endpoint (http/https) 15 | MishkaApiWeb.Endpoint 16 | # Start a worker by calling: MishkaApi.Worker.start_link(arg) 17 | # {MishkaApi.Worker, arg} 18 | ] 19 | 20 | # See https://hexdocs.pm/elixir/Supervisor.html 21 | # for other strategies and supported options 22 | opts = [strategy: :one_for_one, name: MishkaApi.Supervisor] 23 | Supervisor.start_link(children, opts) 24 | end 25 | 26 | # Tell Phoenix to update the endpoint configuration 27 | # whenever the application is updated. 28 | def config_change(changed, _new, removed) do 29 | MishkaApiWeb.Endpoint.config_change(changed, removed) 30 | :ok 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api/plug/user_limiter_plug.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApi.Plug.UserLimiterPlug do 2 | # how many requests user can send per second 3 | end 4 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", MishkaApiWeb.RoomChannel 6 | 7 | # Socket params are passed from the client and can 8 | # be used to verify and authenticate a user. After 9 | # verification, you can put default assigns into 10 | # the socket that will be set for all channels, ie 11 | # 12 | # {:ok, assign(socket, :user_id, verified_user_id)} 13 | # 14 | # To deny connection, return `:error`. 15 | # 16 | # See `Phoenix.Token` documentation for examples in 17 | # performing token verification on connect. 18 | @impl true 19 | def connect(_params, socket, _connect_info) do 20 | {:ok, socket} 21 | end 22 | 23 | # Socket id's are topics that allow you to identify all sockets for a given user: 24 | # 25 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 26 | # 27 | # Would allow you to broadcast a "disconnect" event and terminate 28 | # all active sockets and channels for a given user: 29 | # 30 | # MishkaApiWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 31 | # 32 | # Returning `nil` makes this socket anonymous. 33 | @impl true 34 | def id(_socket), do: nil 35 | end 36 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :mishka_api 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_mishka_api_key", 10 | signing_salt: "AmmcTDEq" 11 | ] 12 | 13 | socket "/socket", MishkaApiWeb.UserSocket, 14 | websocket: true, 15 | longpoll: false 16 | 17 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 18 | 19 | # Serve at "/" the static files from "priv/static" directory. 20 | # 21 | # You should set gzip to true if you are running phx.digest 22 | # when deploying your static files in production. 23 | plug Plug.Static, 24 | at: "/", 25 | from: :mishka_api, 26 | gzip: false, 27 | only: ~w(css fonts images js favicon.ico robots.txt) 28 | 29 | # Code reloading can be explicitly enabled under the 30 | # :code_reloader configuration of your endpoint. 31 | if code_reloading? do 32 | plug Phoenix.CodeReloader 33 | end 34 | 35 | plug Phoenix.LiveDashboard.RequestLogger, 36 | param_key: "request_logger", 37 | cookie_key: "request_logger" 38 | 39 | plug Plug.RequestId 40 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 41 | 42 | plug Plug.Parsers, 43 | parsers: [:urlencoded, :multipart, :json], 44 | pass: ["*/*"], 45 | json_decoder: Phoenix.json_library() 46 | 47 | plug Plug.MethodOverride 48 | plug Plug.Head 49 | plug Plug.Session, @session_options 50 | plug MishkaApiWeb.Router 51 | end 52 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import MishkaApiWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :mishka_api 24 | end 25 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.stop.duration", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.router_dispatch.stop.duration", 29 | tags: [:route], 30 | unit: {:native, :millisecond} 31 | ), 32 | 33 | # VM Metrics 34 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 35 | summary("vm.total_run_queue_lengths.total"), 36 | summary("vm.total_run_queue_lengths.cpu"), 37 | summary("vm.total_run_queue_lengths.io") 38 | ] 39 | end 40 | 41 | defp periodic_measurements do 42 | [ 43 | # A module, function and arguments to be invoked periodically. 44 | # This function must call :telemetry.execute/3 and a metric must be added above. 45 | # {MishkaApiWeb, :count_users, []} 46 | ] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | @doc """ 7 | Translates an error message using gettext. 8 | """ 9 | def translate_error({msg, opts}) do 10 | # When using gettext, we typically pass the strings we want 11 | # to translate as a static argument: 12 | # 13 | # # Translate "is invalid" in the "errors" domain 14 | # dgettext("errors", "is invalid") 15 | # 16 | # # Translate the number of files with plural rules 17 | # dngettext("errors", "1 file", "%{count} files", count) 18 | # 19 | # Because the error messages we show in our forms and APIs 20 | # are defined inside Ecto, we need to translate them dynamically. 21 | # This requires us to call the Gettext module passing our gettext 22 | # backend as first argument. 23 | # 24 | # Note we use the "errors" domain, which means translations 25 | # should be written to the errors.po file. The :count option is 26 | # set by Ecto and indicates we should also apply plural rules. 27 | if count = opts[:count] do 28 | Gettext.dngettext(MishkaApiWeb.Gettext, "errors", msg, msg, count, opts) 29 | else 30 | Gettext.dgettext(MishkaApiWeb.Gettext, "errors", msg, opts) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /apps/mishka_api/lib/mishka_api_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.ErrorView do 2 | use MishkaApiWeb, :view 3 | 4 | # If you want to customize a particular status code 5 | # for a certain format, you may uncomment below. 6 | # def render("500.json", _assigns) do 7 | # %{errors: %{detail: "Internal Server Error"}} 8 | # end 9 | 10 | # By default, Phoenix returns the status message from 11 | # the template name. For example, "404.json" becomes 12 | # "Not Found". 13 | def template_not_found(template, _assigns) do 14 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /apps/mishka_api/test/mishka_api_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.ErrorViewTest do 2 | use MishkaApiWeb.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.json" do 8 | assert render(MishkaApiWeb.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}} 9 | end 10 | 11 | test "renders 500.json" do 12 | assert render(MishkaApiWeb.ErrorView, "500.json", []) == 13 | %{errors: %{detail: "Internal Server Error"}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /apps/mishka_api/test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.ChannelCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | channel tests. 5 | 6 | Such tests rely on `Phoenix.ChannelTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use MishkaApiWeb.ChannelCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with channels 23 | import Phoenix.ChannelTest 24 | import MishkaApiWeb.ChannelCase 25 | 26 | # The default endpoint for testing 27 | @endpoint MishkaApiWeb.Endpoint 28 | end 29 | end 30 | 31 | setup _tags do 32 | :ok 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /apps/mishka_api/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaApiWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use MishkaApiWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with connections 23 | import Plug.Conn 24 | import Phoenix.ConnTest 25 | import MishkaApiWeb.ConnCase 26 | 27 | alias MishkaApiWeb.Router.Helpers, as: Routes 28 | 29 | # The default endpoint for testing 30 | @endpoint MishkaApiWeb.Endpoint 31 | end 32 | end 33 | 34 | setup _tags do 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /apps/mishka_api/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/mishka_content/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/mishka_content/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | mishka_content-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /apps/mishka_content/README.md: -------------------------------------------------------------------------------- 1 | # MishkaContent 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `mishka_content` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:mishka_content, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/mishka_content](https://hexdocs.pm/mishka_content). 21 | 22 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/cache/bookmark_dynamic_supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Cache.BookmarkDynamicSupervisor do 2 | @spec start_job(list() | map() | tuple() | String.t()) :: 3 | :ignore | {:error, any} | {:ok, pid} | {:ok, pid, any} 4 | def start_job(args) do 5 | DynamicSupervisor.start_child( 6 | MishkaContent.Cache.BookmarkOtpRunner, 7 | {MishkaContent.Cache.BookmarkManagement, args} 8 | ) 9 | end 10 | 11 | @spec running_imports :: [any] 12 | 13 | def running_imports() do 14 | match_all = {:"$1", :"$2", :"$3"} 15 | guards = [{:==, :"$3", "user_bookmarks"}] 16 | map_result = [%{id: :"$1", pid: :"$2", type: :"$3"}] 17 | Registry.select(MishkaContent.Cache.BookmarkRegistry, [{match_all, guards, map_result}]) 18 | end 19 | 20 | @spec get_user_pid(String.t()) :: {:error, :get_user_pid} | {:ok, :get_user_pid, pid} 21 | 22 | def get_user_pid(user_id) do 23 | case Registry.lookup(MishkaContent.Cache.BookmarkRegistry, user_id) do 24 | [] -> {:error, :get_user_pid} 25 | [{pid, _type}] -> {:ok, :get_user_pid, pid} 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/cache/content_draft_dynamic_supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Cache.ContentDraftDynamicSupervisor do 2 | def start_job(args) do 3 | DynamicSupervisor.start_child( 4 | MishkaContent.Cache.ContentDraftOtpRunner, 5 | {MishkaContent.Cache.ContentDraftManagement, args} 6 | ) 7 | end 8 | 9 | def running_imports(section: section) do 10 | match_all = {:"$1", :"$2", :"$3"} 11 | guards = [{:==, :"$3", section}] 12 | map_result = [%{id: :"$1", pid: :"$2", section: :"$3"}] 13 | Registry.select(MishkaContent.Cache.ContentDraftRegistry, [{match_all, guards, map_result}]) 14 | end 15 | 16 | def get_draft_pid(id) do 17 | case Registry.lookup(MishkaContent.Cache.ContentDraftRegistry, id) do 18 | [] -> {:error, :get_draft_pid} 19 | [{pid, _type}] -> {:ok, :get_draft_pid, pid} 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/core_plugins/user/login/success_login.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.CorePlugin.Login.SuccessLogin do 2 | alias MishkaInstaller.Reference.OnUserAfterLogin 3 | 4 | use MishkaInstaller.Hook, 5 | module: __MODULE__, 6 | behaviour: OnUserAfterLogin, 7 | event: :on_user_after_login, 8 | initial: [] 9 | 10 | @spec initial(list()) :: {:ok, OnUserAfterLogin.ref(), list()} 11 | def initial(args) do 12 | event = %PluginState{ 13 | name: "MishkaContent.CorePlugin.Login.SuccessLogin", 14 | event: Atom.to_string(@ref), 15 | priority: 2 16 | } 17 | 18 | Hook.register(event: event) 19 | {:ok, @ref, args} 20 | end 21 | 22 | @spec call(OnUserAfterLogin.t()) :: {:reply, OnUserAfterLogin.t()} 23 | def call(%OnUserAfterLogin{} = state) do 24 | create_user_activity(state.user_info, state.ip, state.endpoint) 25 | start_user_bookmarks(state.user_info.id) 26 | {:reply, state} 27 | end 28 | 29 | defp create_user_activity(user_info, user_ip, endpoint) do 30 | MishkaContent.General.Activity.create_activity_by_start_child( 31 | %{ 32 | type: if(endpoint == :html, do: "section", else: "internal_api"), 33 | section: "user", 34 | section_id: user_info.id, 35 | action: "auth", 36 | priority: "high", 37 | status: "info" 38 | }, 39 | %{user_action: "login", user_ip: MishkaInstaller.ip(user_ip), user_id: user_info.id} 40 | ) 41 | end 42 | 43 | def start_user_bookmarks(user_id) do 44 | Task.Supervisor.async_nolink( 45 | MishkaHtmlWeb.AuthController.DeleteCurrentTokenTaskSupervisor, 46 | fn -> 47 | MishkaContent.Cache.BookmarkDynamicSupervisor.start_job( 48 | id: user_id, 49 | type: "user_bookmarks" 50 | ) 51 | end 52 | ) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/core_plugins/user/login/success_logout.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.CorePlugin.Login.SuccessLogout do 2 | alias MishkaInstaller.Reference.OnUserAfterLogout 3 | 4 | use MishkaInstaller.Hook, 5 | module: __MODULE__, 6 | behaviour: OnUserAfterLogout, 7 | event: :on_user_after_logout, 8 | initial: [] 9 | 10 | @spec initial(list()) :: {:ok, OnUserAfterLogout.ref(), list()} 11 | def initial(args) do 12 | event = %PluginState{ 13 | name: "MishkaContent.CorePlugin.Login.SuccessLogout", 14 | event: Atom.to_string(@ref), 15 | priority: 2 16 | } 17 | 18 | Hook.register(event: event) 19 | {:ok, @ref, args} 20 | end 21 | 22 | @spec call(OnUserAfterLogout.t()) :: {:reply, OnUserAfterLogout.t()} 23 | def call(%OnUserAfterLogout{} = state) do 24 | create_user_activity(state.user_id, state.ip, state.endpoint) 25 | {:reply, state} 26 | end 27 | 28 | defp create_user_activity(user_id, user_ip, endpoint) do 29 | MishkaContent.General.Activity.create_activity_by_start_child( 30 | %{ 31 | type: if(endpoint == :html, do: "section", else: "internal_api"), 32 | section: "user", 33 | section_id: nil, 34 | action: "auth", 35 | priority: "high", 36 | status: "info" 37 | }, 38 | %{user_action: "log_out", user_ip: MishkaInstaller.ip(user_ip), user_id: user_id} 39 | ) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/core_plugins/user/role/success_add_role.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.CorePlugin.UserRole.SuccessAddRole do 2 | alias MishkaInstaller.Reference.OnUserAfterSaveRole 3 | 4 | use MishkaInstaller.Hook, 5 | module: __MODULE__, 6 | behaviour: OnUserAfterSaveRole, 7 | event: :on_user_after_save_role, 8 | initial: [] 9 | 10 | @spec initial(list()) :: {:ok, OnUserAfterSaveRole.ref(), list()} 11 | def initial(args) do 12 | event = %PluginState{ 13 | name: "MishkaContent.CorePlugin.UserRole.SuccessAddRole", 14 | event: Atom.to_string(@ref), 15 | priority: 2 16 | } 17 | 18 | Hook.register(event: event) 19 | {:ok, @ref, args} 20 | end 21 | 22 | @spec call(OnUserAfterSaveRole.t()) :: {:reply, OnUserAfterSaveRole.t()} 23 | def call(%OnUserAfterSaveRole{} = state) do 24 | create_user_activity(state.role_id, state.ip, state.endpoint, state.conn) 25 | delete_user_bookmarks(state.conn) 26 | {:reply, state} 27 | end 28 | 29 | defp create_user_activity(role_id, user_ip, endpoint, socket) do 30 | MishkaContent.General.Activity.create_activity_by_start_child( 31 | %{ 32 | type: if(endpoint == :html, do: "section", else: "internal_api"), 33 | section: "role", 34 | section_id: role_id, 35 | action: "add", 36 | priority: "high", 37 | status: "info", 38 | user_id: socket.assigns.user_id 39 | }, 40 | %{user_action: "live_role_create", user_ip: MishkaInstaller.ip(user_ip)} 41 | ) 42 | end 43 | 44 | def delete_user_bookmarks(socket) do 45 | if(!is_nil(Map.get(socket.assigns, :draft_id))) do 46 | MishkaContent.Cache.ContentDraftManagement.delete_record(id: socket.assigns.draft_id) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/email/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Email.Mailer do 2 | use Bamboo.Mailer, otp_app: :mishka_content 3 | end 4 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/general/user_notif_status.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.General.UserNotifStatus do 2 | alias MishkaDatabase.Schema.MishkaContent.UserNotifStatus 3 | 4 | import Ecto.Query 5 | 6 | use MishkaDeveloperTools.DB.CRUD, 7 | module: UserNotifStatus, 8 | error_atom: :user_notif_status, 9 | repo: MishkaDatabase.Repo 10 | 11 | @type data_uuid() :: Ecto.UUID.t() 12 | @type record_input() :: map() 13 | @type error_tag() :: :subscription 14 | @type repo_data() :: Ecto.Schema.t() 15 | @type repo_error() :: Ecto.Changeset.t() 16 | 17 | @behaviour MishkaDeveloperTools.DB.CRUD 18 | 19 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_add, 1} 20 | def create(attrs) do 21 | crud_add(attrs) 22 | end 23 | 24 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_add, 1} 25 | def create(attrs, allowed_fields) do 26 | crud_add(attrs, allowed_fields) 27 | end 28 | 29 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_edit, 1} 30 | def edit(attrs) do 31 | crud_edit(attrs) 32 | end 33 | 34 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_edit, 1} 35 | def edit(attrs, allowed_fields) do 36 | crud_edit(attrs, allowed_fields) 37 | end 38 | 39 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_delete, 1} 40 | def delete(id) do 41 | crud_delete(id) 42 | end 43 | 44 | @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_get_record, 1} 45 | def show_by_id(id) do 46 | crud_get_record(id) 47 | end 48 | 49 | @spec user_read_or_skipped :: Ecto.Query.t() 50 | def user_read_or_skipped() do 51 | from(status in UserNotifStatus, 52 | select: %{notif_id: status.notif_id, user_id: status.user_id, status_type: status.type} 53 | ) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/mishka_content.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent do 2 | def db_content_activity_error(section, action, db_error) do 3 | MishkaContent.General.Activity.create_activity_by_task( 4 | %{ 5 | type: "db", 6 | section: section, 7 | section_id: nil, 8 | action: action, 9 | priority: "high", 10 | status: "error" 11 | }, 12 | %{ 13 | db_rescue_struct: db_error.__struct__, 14 | message: Map.get(db_error, :message), 15 | values: Map.get(db_error, :value), 16 | type: Map.get(db_error, :type) 17 | } 18 | ) 19 | end 20 | 21 | def get_size_of_words(string, count) when not is_nil(string) do 22 | string 23 | |> String.split(" ") 24 | |> Enum.with_index(fn element, index -> if index <= count, do: element end) 25 | |> Enum.reject(fn item -> is_nil(item) end) 26 | |> Enum.join(" ") 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/mishka_content/application.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | alias MishkaContent.CorePlugin.{Login, Register, UserRole} 7 | use Application 8 | 9 | @impl true 10 | def start(_type, _args) do 11 | bookmark_runner_config = [ 12 | strategy: :one_for_one, 13 | name: MishkaContent.Cache.BookmarkOtpRunner 14 | ] 15 | 16 | content_draft_runner_config = [ 17 | strategy: :one_for_one, 18 | name: MishkaContent.Cache.ContentDraftOtpRunner 19 | ] 20 | 21 | children = [ 22 | {Task.Supervisor, name: MishkaContent.Email.EmailHelperTaskSupervisor}, 23 | {Task.Supervisor, name: MishkaContent.General.Notif}, 24 | {Task.Supervisor, name: MishkaContent.General.ActivityTaskSupervisor}, 25 | {Registry, keys: :unique, name: MishkaContent.Cache.BookmarkRegistry}, 26 | {Registry, keys: :unique, name: MishkaContent.Cache.ContentDraftRegistry}, 27 | {DynamicSupervisor, bookmark_runner_config}, 28 | {DynamicSupervisor, content_draft_runner_config}, 29 | %{id: Login.SuccessLogin, start: {Login.SuccessLogin, :start_link, [[]]}}, 30 | %{id: Login.SuccessLogout, start: {Login.SuccessLogout, :start_link, [[]]}}, 31 | %{id: Register.SuccessRegister, start: {Register.SuccessRegister, :start_link, [[]]}}, 32 | %{id: UserRole.SuccessAddRole, start: {UserRole.SuccessAddRole, :start_link, [[]]}} 33 | ] 34 | 35 | # See https://hexdocs.pm/elixir/Supervisor.html 36 | # for other strategies and supported options 37 | opts = [strategy: :one_for_one, name: MishkaContent.Supervisor] 38 | Supervisor.start_link(children, opts) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/social/facebook.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Social.Facebook do 2 | end 3 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/social/sender.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Social.Sender do 2 | end 3 | -------------------------------------------------------------------------------- /apps/mishka_content/lib/social/twitter.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.Social.Twitter do 2 | end 3 | -------------------------------------------------------------------------------- /apps/mishka_content/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaContent.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :mishka_content, 7 | version: "0.0.2", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.13", 13 | compilers: [:gettext | Mix.compilers()], 14 | start_permanent: Mix.env() == :prod, 15 | deps: deps(), 16 | xref: [ 17 | exclude: [MishkaUser.User, MishkaInstaller.Hook] 18 | ] 19 | ] 20 | end 21 | 22 | # Run "mix help compile.app" to learn about applications. 23 | def application do 24 | [ 25 | extra_applications: [ 26 | :logger, 27 | :ecto_sql, 28 | :ecto, 29 | :mishka_database, 30 | :phoenix_pubsub, 31 | :mishka_translator, 32 | :bamboo, 33 | :bamboo_smtp, 34 | :phoenix_html, 35 | :mishka_installer 36 | ], 37 | mod: {MishkaContent.Application, []} 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | {:mishka_installer, "~> 0.0.3"}, 45 | {:mishka_database, in_umbrella: true}, 46 | {:mishka_user, in_umbrella: true}, 47 | {:bamboo, "~> 2.2"}, 48 | {:bamboo_smtp, "~> 4.2"}, 49 | {:bamboo_phoenix, "~> 1.0"}, 50 | {:timex, "~> 3.7"}, 51 | {:mishka_translator, in_umbrella: true} 52 | ] 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /apps/mishka_content/test/mishka_content_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaContentTest do 2 | use ExUnit.Case 3 | doctest MishkaContent 4 | end 5 | -------------------------------------------------------------------------------- /apps/mishka_content/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/mishka_database/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/mishka_database/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | mishka_database-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /apps/mishka_database/README.md: -------------------------------------------------------------------------------- 1 | # MishkaDatabase 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `mishka_database` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:mishka_database, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/mishka_database](https://hexdocs.pm/mishka_database). 21 | 22 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/mishka_database.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase do 2 | @moduledoc """ 3 | Documentation for `MishkaDatabase`. 4 | """ 5 | import Ecto.Changeset 6 | 7 | @spec translate_errors(Ecto.Changeset.t()) :: %{optional(atom) => [binary | map]} 8 | def translate_errors(changeset) do 9 | Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> 10 | Enum.reduce(opts, msg, fn {key, value}, acc -> 11 | String.replace(acc, "%{#{key}}", to_string(value)) 12 | end) 13 | end) 14 | end 15 | 16 | @spec convert_string_map_to_atom_map(map) :: map 17 | def convert_string_map_to_atom_map(map) do 18 | map 19 | |> Map.new(fn {k, v} -> 20 | {String.to_existing_atom(k), v} 21 | end) 22 | end 23 | 24 | @spec validate_binary_id(Ecto.Changeset.t(), atom, any) :: Ecto.Changeset.t() 25 | def validate_binary_id(changeset, field, options \\ []) do 26 | validate_change(changeset, field, fn _, uuid -> 27 | case uuid(uuid) do 28 | {:ok, :uuid, _record_id} -> [] 29 | {:error, :uuid} -> [{field, options[:message] || "ID should be as a UUID type."}] 30 | end 31 | end) 32 | end 33 | 34 | @spec uuid(any) :: {:error, :uuid} | {:ok, :uuid, Ecto.UUID.t()} 35 | def uuid(id) do 36 | case Ecto.UUID.cast(id) do 37 | {:ok, record_id} -> {:ok, :uuid, record_id} 38 | _ -> {:error, :uuid} 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/mishka_database/application.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | MishkaDatabase.Repo 12 | ] 13 | 14 | # See https://hexdocs.pm/elixir/Supervisor.html 15 | # for other strategies and supported options 16 | opts = [strategy: :one_for_one, name: MishkaDatabase.Supervisor, max_restarts: 100] 17 | Supervisor.start_link(children, opts) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/mishka_database/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo do 2 | use Ecto.Repo, 3 | otp_app: :mishka_database, 4 | adapter: Ecto.Adapters.Postgres 5 | 6 | use Scrivener, page_size: 20 7 | end 8 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_content/activity.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaContent.Activity do 2 | use Ecto.Schema 3 | require MishkaTranslator.Gettext 4 | 5 | import Ecto.Changeset 6 | @primary_key {:id, :binary_id, autogenerate: true} 7 | @foreign_key_type :binary_id 8 | 9 | schema "activities" do 10 | field(:type, ActivitiesTypeEnum) 11 | field(:section, ActivitiesSection) 12 | field(:section_id, :binary_id, primary_key: false) 13 | field(:priority, ContentPriorityEnum) 14 | field(:status, ActivitiesStatusEnum) 15 | field(:action, ActivitiesAction) 16 | field(:extra, :map) 17 | 18 | timestamps(type: :utc_datetime) 19 | end 20 | 21 | @all_fields ~w(type section section_id priority status action extra)a 22 | @all_required ~w(type section priority status action)a 23 | 24 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 25 | def changeset(struct, params \\ %{}) do 26 | struct 27 | |> cast(params, @all_fields) 28 | |> validate_required(@all_required, 29 | message: 30 | MishkaTranslator.Gettext.dgettext("db_schema_content", "فیلد مذکور نمی تواند خالی باشد") 31 | ) 32 | |> MishkaDatabase.validate_binary_id(:section_id) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_content/blog_link.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaContent.BlogLink do 2 | use Ecto.Schema 3 | 4 | require MishkaTranslator.Gettext 5 | import Ecto.Changeset 6 | @primary_key {:id, :binary_id, autogenerate: true} 7 | @foreign_key_type :binary_id 8 | 9 | schema "blog_links" do 10 | field(:short_description, :string) 11 | field(:status, ContentStatusEnum) 12 | field(:type, BlogLinkType) 13 | field(:title, :string) 14 | field(:link, :string) 15 | field(:short_link, :string) 16 | 17 | field(:robots, ContentRobotsEnum, default: :IndexFollow) 18 | 19 | field(:section_id, :binary_id) 20 | 21 | timestamps(type: :utc_datetime) 22 | end 23 | 24 | @all_fields ~w( 25 | short_description status type title link short_link robots section_id 26 | )a 27 | 28 | @all_required ~w( 29 | short_description status type title link robots section_id 30 | )a 31 | 32 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 33 | def changeset(struct, params \\ %{}) do 34 | struct 35 | |> cast(params, @all_fields) 36 | |> validate_required(@all_required, 37 | message: 38 | MishkaTranslator.Gettext.dgettext("db_schema_content", "فیلد مذکور نمی تواند خالی باشد") 39 | ) 40 | |> MishkaDatabase.validate_binary_id(:section_id) 41 | |> unique_constraint(:short_link, 42 | name: :index_blog_links_on_short_link, 43 | message: 44 | MishkaTranslator.Gettext.dgettext( 45 | "db_schema_content", 46 | "این لینک کوتاه از قبل انتخاب شده است. لطفا لینک دیگری را وارد کنید." 47 | ) 48 | ) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_content/bookmark.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaContent.Bookmark do 2 | use Ecto.Schema 3 | 4 | require MishkaTranslator.Gettext 5 | import Ecto.Changeset 6 | @primary_key {:id, :binary_id, autogenerate: true} 7 | @foreign_key_type :binary_id 8 | 9 | schema "bookmarks" do 10 | field(:status, ContentStatusEnum, default: :active) 11 | field(:section, BookmarkSection) 12 | field(:section_id, :binary_id, primary_key: false) 13 | field(:extra, :map) 14 | 15 | belongs_to(:users, MishkaDatabase.Schema.MishkaUser.User, 16 | foreign_key: :user_id, 17 | type: :binary_id 18 | ) 19 | 20 | timestamps(type: :utc_datetime) 21 | end 22 | 23 | @all_fields ~w(status section section_id extra user_id)a 24 | @all_required ~w(status section section_id user_id)a 25 | 26 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 27 | def changeset(struct, params \\ %{}) do 28 | struct 29 | |> cast(params, @all_fields) 30 | |> validate_required(@all_required, 31 | message: 32 | MishkaTranslator.Gettext.dgettext("db_schema_content", "فیلد مذکور نمی تواند خالی باشد") 33 | ) 34 | |> MishkaDatabase.validate_binary_id(:user_id) 35 | |> MishkaDatabase.validate_binary_id(:section_id) 36 | |> foreign_key_constraint(:user_id, 37 | message: 38 | MishkaTranslator.Gettext.dgettext( 39 | "db_schema_content", 40 | "ممکن است فیلد مذکور اشتباه باشد یا برای حذف آن اگر اقدام می کنید برای آن وابستگی وجود داشته باشد" 41 | ) 42 | ) 43 | |> unique_constraint(:section, 44 | name: :index_bookmarks_on_section_and_section_id_and_user_id, 45 | message: 46 | MishkaTranslator.Gettext.dgettext("db_schema_content", "این بخش قبلا بوکمارک شده است") 47 | ) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_content/content_schema_enum.ex: -------------------------------------------------------------------------------- 1 | import EctoEnum 2 | defenum(ContentStatusEnum, inactive: 0, active: 1, archived: 2, soft_delete: 3) 3 | defenum(ContentPriorityEnum, none: 0, low: 1, medium: 2, high: 3, featured: 4) 4 | defenum(ContentRobotsEnum, IndexFollow: 0, IndexNoFollow: 1, NoIndexFollow: 2, NoIndexNoFollow: 3) 5 | defenum(CategoryVisibility, show: 0, invisibel: 1, test_show: 2, test_invisibel: 3) 6 | defenum(PostVisibility, show: 0, invisibel: 1, test_show: 2, test_invisibel: 3) 7 | defenum(CommentSection, blog_post: 0) 8 | defenum(SubscriptionSection, blog_post: 0, blog_category: 1) 9 | defenum(BlogLinkType, bottom: 0, inside: 1, featured: 2) 10 | defenum(ActivitiesStatusEnum, error: 0, info: 1, warning: 2, report: 3, throw: 4, exit: 5) 11 | 12 | defenum(ActivitiesTypeEnum, 13 | section: 0, 14 | email: 1, 15 | internal_api: 2, 16 | external_api: 3, 17 | html_router: 4, 18 | api_router: 5, 19 | db: 6, 20 | plugin: 7 21 | ) 22 | 23 | defenum(ActivitiesSection, 24 | blog_post: 0, 25 | blog_category: 1, 26 | comment: 2, 27 | tag: 3, 28 | other: 4, 29 | blog_author: 5, 30 | blog_post_like: 6, 31 | blog_tag_mapper: 7, 32 | blog_link: 8, 33 | blog_tag: 9, 34 | activity: 10, 35 | bookmark: 11, 36 | comment_like: 12, 37 | notif: 13, 38 | subscription: 14, 39 | setting: 15, 40 | permission: 16, 41 | role: 17, 42 | user_role: 18, 43 | identity: 19, 44 | user: 20 45 | ) 46 | 47 | defenum(ActivitiesAction, 48 | add: 0, 49 | edit: 1, 50 | delete: 2, 51 | destroy: 3, 52 | read: 4, 53 | send_request: 5, 54 | receive_request: 6, 55 | other: 7, 56 | auth: 8 57 | ) 58 | 59 | defenum(BookmarkSection, blog_post: 0) 60 | defenum(NotifSection, blog_post: 0, admin: 1, user_only: 3, public: 4) 61 | defenum(NotifStatusType, read: 0, skipped: 1) 62 | defenum(NotifType, client: 0, admin: 1) 63 | defenum(NotifTarget, all: 0, mobile: 1, android: 2, ios: 3, cli: 4) 64 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_content/subscription.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaContent.Subscription do 2 | use Ecto.Schema 3 | 4 | require MishkaTranslator.Gettext 5 | import Ecto.Changeset 6 | @primary_key {:id, :binary_id, autogenerate: true} 7 | @foreign_key_type :binary_id 8 | 9 | schema "subscriptions" do 10 | field(:status, ContentStatusEnum, default: :active) 11 | field(:section, SubscriptionSection) 12 | field(:section_id, :binary_id, primary_key: false) 13 | field(:expire_time, :utc_datetime) 14 | field(:extra, :map) 15 | 16 | belongs_to(:users, MishkaDatabase.Schema.MishkaUser.User, 17 | foreign_key: :user_id, 18 | type: :binary_id 19 | ) 20 | 21 | timestamps(type: :utc_datetime) 22 | end 23 | 24 | @all_fields ~w(status section section_id expire_time extra user_id)a 25 | @all_required ~w(status section section_id user_id)a 26 | 27 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 28 | def changeset(struct, params \\ %{}) do 29 | struct 30 | |> cast(params, @all_fields) 31 | |> validate_required(@all_required, 32 | message: 33 | MishkaTranslator.Gettext.dgettext("db_schema_content", "فیلد مذکور نمی تواند خالی باشد") 34 | ) 35 | |> MishkaDatabase.validate_binary_id(:section_id) 36 | |> MishkaDatabase.validate_binary_id(:user_id) 37 | |> foreign_key_constraint(:user_id, 38 | message: 39 | MishkaTranslator.Gettext.dgettext( 40 | "db_schema_content", 41 | "ممکن است فیلد مذکور اشتباه باشد یا برای حذف آن اگر اقدام می کنید برای آن وابستگی وجود داشته باشد" 42 | ) 43 | ) 44 | |> unique_constraint(:section, 45 | name: :index_subscriptions_on_section_and_section_id_and_user_id, 46 | message: 47 | MishkaTranslator.Gettext.dgettext( 48 | "db_schema_content", 49 | "شما از قبل در این بخش مشترک شده اید." 50 | ) 51 | ) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_user/permission.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaUser.Permission do 2 | use Ecto.Schema 3 | require MishkaTranslator.Gettext 4 | import Ecto.Changeset 5 | @primary_key {:id, :binary_id, autogenerate: true} 6 | @foreign_key_type :binary_id 7 | 8 | schema "permissions" do 9 | field(:value, :string) 10 | 11 | belongs_to(:roles, MishkaDatabase.Schema.MishkaUser.Role, 12 | foreign_key: :role_id, 13 | type: :binary_id 14 | ) 15 | 16 | timestamps(type: :utc_datetime) 17 | end 18 | 19 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 20 | def changeset(struct, params \\ %{}) do 21 | struct 22 | |> cast(params, [:value, :role_id]) 23 | |> validate_required([:value, :role_id], 24 | message: 25 | MishkaTranslator.Gettext.dgettext("db_schema_user", "فیلد مذکور نمی تواند خالی باشد") 26 | ) 27 | |> MishkaDatabase.validate_binary_id(:role_id) 28 | |> foreign_key_constraint(:role_id, 29 | message: 30 | MishkaTranslator.Gettext.dgettext( 31 | "db_schema_user", 32 | "ممکن است فیلد مذکور اشتباه باشد یا برای حذف آن اگر اقدام می کنید برای آن وابستگی وجود داشته باشد" 33 | ) 34 | ) 35 | |> unique_constraint(:value, 36 | name: :index_permissions_on_value_and_role_id, 37 | message: 38 | MishkaTranslator.Gettext.dgettext( 39 | "db_schema_user", 40 | "این سطح دسترسی از قبل برای نقش مذکور اضافه شده است" 41 | ) 42 | ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_user/role.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaUser.Role do 2 | use Ecto.Schema 3 | require MishkaTranslator.Gettext 4 | import Ecto.Changeset 5 | @primary_key {:id, :binary_id, autogenerate: true} 6 | @foreign_key_type :binary_id 7 | 8 | schema "roles" do 9 | field(:name, :string) 10 | field(:display_name, :string) 11 | 12 | has_many(:users_roles, MishkaDatabase.Schema.MishkaUser.UserRole, 13 | foreign_key: :role_id, 14 | on_delete: :delete_all 15 | ) 16 | 17 | has_many(:permissions, MishkaDatabase.Schema.MishkaUser.Permission, 18 | foreign_key: :role_id, 19 | on_delete: :delete_all 20 | ) 21 | 22 | timestamps(type: :utc_datetime) 23 | end 24 | 25 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 26 | def changeset(struct, params \\ %{}) do 27 | struct 28 | |> cast(params, [:name, :display_name]) 29 | |> validate_required([:name, :display_name], 30 | message: 31 | MishkaTranslator.Gettext.dgettext("db_schema_user", "فیلد مذکور نمی تواند خالی باشد") 32 | ) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_user/user_schema_enum.ex: -------------------------------------------------------------------------------- 1 | import EctoEnum 2 | 3 | defenum(UserIdentityProviderEnum, self: 0, google: 1, github: 2, facebook: 3, twitter: 4, apple: 5) 4 | 5 | defenum(UserStatusEnum, registered: 0, active: 1, inactive: 2, archived: 3) 6 | defenum(UserTokenTypeEnum, refresh: 0, access: 1) 7 | -------------------------------------------------------------------------------- /apps/mishka_database/lib/schema/mishka_user/user_token.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Schema.MishkaUser.UserToken do 2 | use Ecto.Schema 3 | 4 | require MishkaTranslator.Gettext 5 | import Ecto.Changeset 6 | @primary_key {:id, :binary_id, autogenerate: false} 7 | @foreign_key_type :binary_id 8 | 9 | schema "user_tokens" do 10 | field(:token, :string) 11 | field(:type, UserTokenTypeEnum) 12 | field(:expire_time, :utc_datetime) 13 | field(:extra, :map) 14 | 15 | belongs_to(:users, MishkaDatabase.Schema.MishkaUser.User, 16 | foreign_key: :user_id, 17 | type: :binary_id 18 | ) 19 | 20 | timestamps(type: :utc_datetime) 21 | end 22 | 23 | @all_fields ~w(id token type expire_time extra user_id)a 24 | @spec changeset(struct(), map()) :: Ecto.Changeset.t() 25 | def changeset(struct, params \\ %{}) do 26 | struct 27 | |> cast(params, @all_fields) 28 | |> validate_required(@all_fields, 29 | message: 30 | MishkaTranslator.Gettext.dgettext("db_schema_user", "فیلد مذکور نمی تواند خالی باشد") 31 | ) 32 | |> foreign_key_constraint(:user_id, 33 | message: 34 | MishkaTranslator.Gettext.dgettext( 35 | "db_schema_user", 36 | "ممکن است فیلد مذکور اشتباه باشد یا برای حذف آن اگر اقدام می کنید برای آن وابستگی وجود داشته باشد" 37 | ) 38 | ) 39 | |> unique_constraint(:token, 40 | name: :index_token_on_user_tokens, 41 | message: 42 | MishkaTranslator.Gettext.dgettext( 43 | "db_schema_user", 44 | "این ایمیل از قبل در سیستم ثبت شده است." 45 | ) 46 | ) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /apps/mishka_database/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :mishka_database, 7 | version: "0.0.2", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | aliases: aliases(), 12 | lockfile: "../../mix.lock", 13 | elixir: "~> 1.13", 14 | compilers: [:gettext | Mix.compilers()], 15 | start_permanent: Mix.env() == :prod, 16 | deps: deps() 17 | ] 18 | end 19 | 20 | # Run "mix help compile.app" to learn about applications. 21 | def application do 22 | [ 23 | extra_applications: [:logger, :mishka_translator, :phoenix_pubsub], 24 | mod: {MishkaDatabase.Application, []} 25 | ] 26 | end 27 | 28 | # Run "mix help deps" to learn about dependencies. 29 | defp deps do 30 | [ 31 | {:mishka_developer_tools, "~> 0.0.6"}, 32 | {:comeonin, "~> 5.3"}, 33 | {:bcrypt_elixir, "~> 2.3"}, 34 | {:ecto_enum, "~> 1.4"}, 35 | {:jason, "~> 1.2"}, 36 | {:scrivener_ecto, "~> 2.7"}, 37 | {:mishka_translator, in_umbrella: true} 38 | ] 39 | end 40 | 41 | defp aliases do 42 | [ 43 | test: ["ecto.create --quiet", "ecto.migrate", "test"] 44 | ] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210111193110_users.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Users do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | add(:full_name, :string, size: 60, null: false) 8 | add(:username, :string, size: 20, null: true, unique: true) 9 | add(:email, :string, null: false, unique: true) 10 | add(:password_hash, :string, null: true) 11 | add(:status, :integer, null: false) 12 | add(:unconfirmed_email, :string, size: 120, null: true, unique: true) 13 | timestamps() 14 | end 15 | create( 16 | index(:users, [:email], 17 | name: :index_users_on_email, 18 | unique: true 19 | ) 20 | ) 21 | create( 22 | index(:users, [:unconfirmed_email], 23 | name: :index_users_on_verified_email, 24 | unique: true 25 | ) 26 | ) 27 | create( 28 | index(:users, [:username], 29 | name: :index_users_on_username, 30 | unique: true 31 | ) 32 | ) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210328064612_identities.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Identities do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:identities, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | 9 | add(:provider_uid, :string, null: true) 10 | add(:token, :string, null: true) 11 | add(:identity_provider, :integer, null: false) 12 | 13 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 14 | timestamps() 15 | end 16 | create( 17 | index(:identities, [:provider_uid, :identity_provider], 18 | name: :index_identities_on_provider_uid_and_identity_provider, 19 | unique: true 20 | ) 21 | ) 22 | 23 | create( 24 | index(:identities, [:user_id, :identity_provider], 25 | name: :index_identities_on_user_id_and_identity_provider, 26 | unique: true 27 | ) 28 | ) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210328103652_roles.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Roles do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:roles, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | 9 | add(:name, :string, null: false) 10 | add(:display_name, :string, null: false) 11 | 12 | timestamps() 13 | end 14 | create( 15 | index(:roles, [:display_name], 16 | name: :index_roles_on_display_name, 17 | unique: true 18 | ) 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210328103704_permissions.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Permissions do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:permissions, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | 9 | add(:value, :string, null: false) 10 | add(:role_id, references(:roles, on_delete: :delete_all, type: :uuid), null: false) 11 | timestamps() 12 | end 13 | create( 14 | index(:permissions, [:value, :role_id], 15 | name: :index_permissions_on_value_and_role_id, 16 | unique: true 17 | ) 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330072832_blog_tags.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.BlogTags do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blog_tags, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | add(:title, :string, size: 200, null: false) 8 | 9 | 10 | add(:alias_link, :string, size: 200, null: false) 11 | add(:meta_keywords, :string, size: 200, null: true) 12 | add(:meta_description, :string, size: 164, null: true) 13 | add(:custom_title, :string, size: 200, null: true) 14 | add(:robots, :integer, null: false) 15 | 16 | timestamps() 17 | end 18 | create( 19 | index(:blog_tags, [:alias_link], 20 | # concurrently: true, 21 | name: :index_blog_tags_on_alias_link, 22 | unique: true 23 | ) 24 | ) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330072837_blog_tags_mappers.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.BlogTagsMappers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blog_tags_mappers, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:post_id, references(:blog_posts, on_delete: :nothing, type: :uuid)) 9 | add(:tag_id, references(:blog_tags, on_delete: :nothing, type: :uuid)) 10 | 11 | timestamps() 12 | end 13 | create( 14 | index(:blog_tags_mappers, [:post_id, :tag_id], 15 | name: :index_blog_tags_mappers_on_post_id_and_tag_id, 16 | unique: true 17 | ) 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330132342_blog_authors.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.BlogAuthors do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blog_authors, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:post_id, references(:blog_posts, on_delete: :nothing, type: :uuid)) 9 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 10 | 11 | timestamps() 12 | end 13 | create( 14 | index(:blog_authors, [:post_id, :user_id], 15 | name: :index_blog_authors_on_post_id_and_user_id, 16 | unique: true 17 | ) 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330132407_blog_likes.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.BlogLikes do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blog_likes, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:post_id, references(:blog_posts, on_delete: :nothing, type: :uuid)) 9 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 10 | 11 | timestamps() 12 | end 13 | create( 14 | index(:blog_likes, [:post_id, :user_id], 15 | name: :index_blog_likes_on_post_id_and_user_id, 16 | unique: true 17 | ) 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330141536_blog_links.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.BlogLinks do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blog_links, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:short_description, :text, null: true) 9 | add(:status, :integer, null: false) 10 | add(:type, :integer, null: false) 11 | add(:title, :string, size: 200, null: false) 12 | add(:link, :string, size: 350, null: false) 13 | add(:short_link, :string, size: 350, null: true) 14 | 15 | add(:section_id, :uuid, primary_key: false, null: false) 16 | 17 | add(:robots, :integer, null: false) 18 | 19 | timestamps() 20 | end 21 | create( 22 | index(:blog_links, [:short_link], 23 | name: :index_blog_links_on_short_link, 24 | unique: true 25 | ) 26 | ) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330142326_subscriptions.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Subscriptions do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:subscriptions, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:status, :integer, null: false) 9 | add(:section, :integer, null: false, null: false) 10 | add(:section_id, :uuid, primary_key: false, null: false) 11 | add(:expire_time, :utc_datetime, null: true) 12 | add(:extra, :map, null: true) 13 | 14 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 15 | timestamps() 16 | end 17 | create( 18 | index(:subscriptions, [:section, :section_id, :user_id], 19 | name: :index_subscriptions_on_section_and_section_id_and_user_id, 20 | unique: true 21 | ) 22 | ) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330142350_bookmarks.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Bookmarks do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:bookmarks, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:status, :integer, null: false) 9 | add(:section, :integer, null: false, null: false) 10 | add(:section_id, :uuid, primary_key: false, null: false) 11 | add(:extra, :map, null: true) 12 | 13 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 14 | timestamps() 15 | end 16 | create( 17 | index(:bookmarks, [:section, :section_id, :user_id], 18 | name: :index_bookmarks_on_section_and_section_id_and_user_id, 19 | unique: true 20 | ) 21 | ) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330142430_notifs.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Notifs do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:notifs, primary_key: false) do 6 | 7 | add(:id, :uuid, primary_key: true) 8 | 9 | add(:status, :integer, null: false) 10 | add(:section, :integer, null: false) 11 | add(:section_id, :uuid, primary_key: false, null: true) 12 | add(:type, :integer, null: false) 13 | add(:target, :integer, null: false) 14 | add(:title, :string, size: 350, null: false) 15 | add(:description, :text, null: false) 16 | add(:expire_time, :utc_datetime, null: true) 17 | add(:extra, :map, null: true) 18 | 19 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 20 | 21 | timestamps() 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330162305_comments.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.Comments do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:comments, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | # comment basic info 9 | add(:description, :text, null: false) 10 | add(:status, :integer, null: false) 11 | add(:priority, :integer, null: false) 12 | add(:section, :integer, null: false, null: false) 13 | add(:section_id, :uuid, primary_key: false, null: false) 14 | add(:sub, :uuid, primary_key: false, null: true) 15 | 16 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 17 | timestamps() 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210330224243_comments_likes.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.CommentsLikes do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:comments_likes, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid)) 9 | add(:comment_id, references(:comments, on_delete: :nothing, type: :uuid)) 10 | 11 | 12 | timestamps() 13 | end 14 | create( 15 | index(:comments_likes, [:user_id, :comment_id], 16 | name: :index_comments_on_likes, 17 | unique: true 18 | ) 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20210402080613_users_roles.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.UsersRoles do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users_roles, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | 8 | add(:role_id, references(:roles, type: :uuid), null: false) 9 | add(:user_id, references(:users, type: :uuid), null: false) 10 | timestamps() 11 | end 12 | create( 13 | index(:users_roles, [:role_id, :user_id], 14 | name: :index_users_roles_on_role_id_and_user_id, 15 | unique: true 16 | ) 17 | ) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20211005180905_user_notif_statuses.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.UserNotifStatuses do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:user_notif_statuses, primary_key: false) do 6 | 7 | add(:id, :uuid, primary_key: true) 8 | add(:type, :integer, null: false) 9 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid), null: false) 10 | add(:notif_id, references(:notifs, on_delete: :delete_all, type: :uuid), null: false) 11 | 12 | timestamps() 13 | end 14 | create( 15 | index(:user_notif_statuses, [:user_id, :notif_id], 16 | name: :index_user_notif_on_user_notif_statuses, 17 | unique: true 18 | ) 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/mishka_database/priv/repo/migrations/20220626144233_user_tokens.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaDatabase.Repo.Migrations.UserTokens do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:user_tokens, primary_key: false) do 6 | add(:id, :uuid, primary_key: true) 7 | add(:token, :text, null: false) 8 | add(:type, :integer, null: false) 9 | add(:expire_time, :utc_datetime, null: false) 10 | add(:extra, :map, null: false) 11 | 12 | add(:user_id, references(:users, on_delete: :nothing, type: :uuid), null: false) 13 | timestamps() 14 | end 15 | create( 16 | index(:user_tokens, [:token], 17 | name: :index_token_on_user_tokens, 18 | unique: true 19 | ) 20 | ) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /apps/mishka_database/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/mishka_file/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/mishka_file/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | mishka_file-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /apps/mishka_file/README.md: -------------------------------------------------------------------------------- 1 | # MishkaFile 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `mishka_file` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:mishka_file, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/mishka_file](https://hexdocs.pm/mishka_file). 21 | 22 | -------------------------------------------------------------------------------- /apps/mishka_file/lib/mishka_file.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaFile do 2 | end 3 | -------------------------------------------------------------------------------- /apps/mishka_file/lib/mishka_file/application.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaFile.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Starts a worker by calling: MishkaFile.Worker.start_link(arg) 12 | # {MishkaFile.Worker, arg} 13 | ] 14 | 15 | # See https://hexdocs.pm/elixir/Supervisor.html 16 | # for other strategies and supported options 17 | opts = [strategy: :one_for_one, name: MishkaFile.Supervisor] 18 | Supervisor.start_link(children, opts) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/mishka_file/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaFile.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :mishka_file, 7 | version: "0.0.2", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.13", 13 | compilers: [:gettext | Mix.compilers()], 14 | start_permanent: Mix.env() == :prod, 15 | deps: deps() 16 | ] 17 | end 18 | 19 | # Run "mix help compile.app" to learn about applications. 20 | def application do 21 | [ 22 | extra_applications: [:logger, :mishka_translator], 23 | mod: {MishkaFile.Application, []} 24 | ] 25 | end 26 | 27 | # Run "mix help deps" to learn about dependencies. 28 | defp deps do 29 | [ 30 | {:mishka_translator, in_umbrella: true} 31 | ] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /apps/mishka_file/test/mishka_file_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MishkaFileTest do 2 | use ExUnit.Case 3 | doctest MishkaFile 4 | end 5 | -------------------------------------------------------------------------------- /apps/mishka_file/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/mishka_html/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /apps/mishka_html/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | mishka_html-*.tar 24 | 25 | # If NPM crashes, it generates a log, let's ignore it too. 26 | npm-debug.log 27 | 28 | # The directory NPM downloads your dependencies sources to. 29 | /assets/node_modules/ 30 | 31 | # Since we are building assets from assets/, 32 | # we ignore priv/static. You may want to comment 33 | # this depending on your deployment strategy. 34 | /priv/static/assets/ 35 | /priv/static/uploads/ 36 | -------------------------------------------------------------------------------- /apps/mishka_html/README.md: -------------------------------------------------------------------------------- 1 | # MishkaHtml 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Install Node.js dependencies with `npm install` inside the `assets` directory 7 | * Start Phoenix endpoint with `mix phx.server` 8 | 9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 10 | 11 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 12 | 13 | ## Learn more 14 | 15 | * Official website: https://www.phoenixframework.org/ 16 | * Guides: https://hexdocs.pm/phoenix/overview.html 17 | * Docs: https://hexdocs.pm/phoenix 18 | * Forum: https://elixirforum.com/c/phoenix-forum 19 | * Source: https://github.com/phoenixframework/phoenix 20 | -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/css/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Iconly-bulk'; 3 | src: url('../fonts/Iconly-bulk.eot?7ldoi4'); 4 | src: url('../fonts/Iconly-bulk.eot?7ldoi4#iefix') format('embedded-opentype'), 5 | url('../fonts/Iconly-bulk.ttf?7ldoi4') format('truetype'), 6 | url('../fonts/Iconly-bulk.woff?7ldoi4') format('woff'), 7 | url('../fonts/Iconly-bulk.svg?7ldoi4#Iconly-bulk') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | font-display: block; 11 | } 12 | 13 | @font-face { 14 | font-family: 'Vazir-Regular'; 15 | src: url('../fonts/Vazir-Regular.eot?7ldoi4'); 16 | src: url('../fonts/Vazir-Regular.eot?7ldoi4#iefix') format('embedded-opentype'), 17 | url('../fonts/Vazir-Regular.ttf?7ldoi4') format('truetype'), 18 | url('../fonts/Vazir-Regular.woff?7ldoi4') format('woff'); 19 | 20 | 21 | font-weight: normal; 22 | font-style: normal; 23 | font-display: block; 24 | } -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/favicon.ico -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Iconly-bulk.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Iconly-bulk.eot -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Iconly-bulk.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Iconly-bulk.ttf -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Iconly-bulk.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Iconly-bulk.woff -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Iconly-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Iconly-light.eot -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Vazir-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Vazir-Regular.eot -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Vazir-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Vazir-Regular.ttf -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Vazir-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Vazir-Regular.woff -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/fonts/Vazir-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/fonts/Vazir-Regular.woff2 -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/1.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/2.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/3.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/4.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/5.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/icons8-login-as-user-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/icons8-login-as-user-80.png -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/mylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/mylogo.png -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/images/no-user-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahryarjb/mishka-cms/45563d985f98217f57e25829375835d93f540a6e/apps/mishka_html/assets/static/images/no-user-image.jpg -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/check_editor_extended.js: -------------------------------------------------------------------------------- 1 | const classCheckEditor = require('./ckeditor'); 2 | 3 | classCheckEditor.A.prototype.cleanup = function() { 4 | 5 | } 6 | 7 | module.exports = classCheckEditor; -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/af.js: -------------------------------------------------------------------------------- 1 | (function(e){const n=e["af"]=e["af"]||{};n.dictionary=Object.assign(n.dictionary||{},{"Align center":"Belyn in die middel","Align left":"Belyn links","Align right":"Belyn regs","Block quote":"Blok-aanhaling",Bold:"Vetgedruk",Cancel:"Kanselleer",Code:"Kode",Italic:"Skuinsgedruk",Justify:"Belyn beide kante","Remove color":"","Remove Format":"Verwyder formatering","Restore default":"",Save:"Berg","Show more items":"",Strikethrough:"Deurgetrek","Text alignment":"Teksbelyning","Text alignment toolbar":"",Underline:"Onderstreep"});n.getPluralForm=function(e){return e!=1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/ca.js: -------------------------------------------------------------------------------- 1 | (function(a){const e=a["ca"]=a["ca"]||{};e.dictionary=Object.assign(e.dictionary||{},{"Align center":"Alineació centre","Align left":"Alineació esquerra","Align right":"Alineació dreta",Big:"Gran","Block quote":"Cita de bloc","Blue marker":"Marcador blau",Bold:"Negreta",Cancel:"Cancel·lar","Choose heading":"Escull capçalera",Code:"Codi",Default:"Predeterminada","Document colors":"","Font Background Color":"","Font Color":"","Font Size":"Mida de la font","Green marker":"Marcador verd","Green pen":"Bolígraf verd",Heading:"Capçalera","Heading 1":"Capçalera 1","Heading 2":"Capçalera 2","Heading 3":"Capçalera 3","Heading 4":"","Heading 5":"","Heading 6":"",Highlight:"Destacat",Huge:"Molt gran",Italic:"Cursiva",Justify:"Justificar",Paragraph:"Pàrraf","Pink marker":"Marcador rosa","Red pen":"Marcador vermell","Remove color":"","Remove highlight":"Esborrar destacat","Restore default":"",Save:"Desar","Show more items":"",Small:"Peita",Strikethrough:"Marcat","Text alignment":"Alineació text","Text alignment toolbar":"","Text highlight toolbar":"",Tiny:"Molt petita",Underline:"Subrallat","Yellow marker":"Marcador groc"});e.getPluralForm=function(a){return a!=1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/gu.js: -------------------------------------------------------------------------------- 1 | (function(n){const o=n["gu"]=n["gu"]||{};o.dictionary=Object.assign(o.dictionary||{},{"Block quote":" વિચાર ટાંકો",Bold:"ઘાટુ - બોલ્ડ્",Code:"",Italic:"ત્રાંસુ - ઇટલિક્",Strikethrough:"",Underline:"નીચે લિટી - અન્ડરલાઇન્"});o.getPluralForm=function(n){return n!=1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/kk.js: -------------------------------------------------------------------------------- 1 | (function(n){const t=n["kk"]=n["kk"]||{};t.dictionary=Object.assign(t.dictionary||{},{"Align center":"Ортадан туралау","Align left":"Солға туралау","Align right":"Оңға туралау",Justify:"","Text alignment":"Мәтінді туралау","Text alignment toolbar":"Мәтінді туралау құралдар тақтасы"});t.getPluralForm=function(n){return n!=1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/oc.js: -------------------------------------------------------------------------------- 1 | (function(o){const e=o["oc"]=o["oc"]||{};e.dictionary=Object.assign(e.dictionary||{},{Bold:"Gras",Cancel:"Anullar",Code:"",Italic:"Italica","Remove color":"","Restore default":"",Save:"Enregistrar","Show more items":"",Strikethrough:"",Underline:""});e.getPluralForm=function(o){return o>1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/si.js: -------------------------------------------------------------------------------- 1 | (function(e){const t=e["si"]=e["si"]||{};t.dictionary=Object.assign(t.dictionary||{},{Bold:"තදකුරු","Break text":"","Bulleted List":"බුලටිත ලැයිස්තුව","Bulleted list styles toolbar":"","Centered image":"","Change image text alternative":"",Circle:"",Code:"",Decimal:"","Decimal with leading zero":"",Disc:"","Enter image caption":"","Full size image":"","Image resize list":"","Image toolbar":"","image widget":"","In line":"",Insert:"","Insert image":"පින්තූරය ඇතුල් කරන්න","Insert image via URL":"",Italic:"ඇලකුරු","Left aligned image":"","Lower-latin":"","Lower–roman":"","Numbered List":"අංකිත ලැයිස්තුව","Numbered list styles toolbar":"",Original:"",Redo:"නැවත කරන්න","Resize image":"","Resize image to %0":"","Resize image to the original size":"","Right aligned image":"","Side image":"",Square:"",Strikethrough:"","Text alternative":"","To-do List":"","Toggle the circle list style":"","Toggle the decimal list style":"","Toggle the decimal with leading zero list style":"","Toggle the disc list style":"","Toggle the lower–latin list style":"","Toggle the lower–roman list style":"","Toggle the square list style":"","Toggle the upper–latin list style":"","Toggle the upper–roman list style":"",Underline:"",Undo:"අහෝසි කරන්න",Update:"","Update image URL":"","Upload failed":"උඩුගත කිරීම අසාර්ථක විය","Upper-latin":"","Upper-roman":"","Wrap text":""});t.getPluralForm=function(e){return e!=1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/js/translations/tt.js: -------------------------------------------------------------------------------- 1 | (function(o){const t=o["tt"]=o["tt"]||{};t.dictionary=Object.assign(t.dictionary||{},{Bold:"Калын",Cancel:"",Code:"Код",Italic:"",Redo:"Кабатла","Remove color":"","Restore default":"",Save:"Сакла","Show more items":"",Strikethrough:"",Underline:"",Undo:""});t.getPluralForm=function(o){return 0}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})); -------------------------------------------------------------------------------- /apps/mishka_html/assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html/application.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtml.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | children = [ 10 | # Start the Telemetry supervisor 11 | MishkaHtmlWeb.Telemetry, 12 | # Start the PubSub system 13 | {Phoenix.PubSub, name: MishkaHtml.PubSub}, 14 | {Task.Supervisor, name: MishkaHtmlWeb.AuthController.DeleteCurrentTokenTaskSupervisor}, 15 | # Start the Endpoint (http/https) 16 | MishkaHtmlWeb.Endpoint 17 | ] 18 | 19 | # See https://hexdocs.pm/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: MishkaHtml.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | 25 | # Tell Phoenix to update the endpoint configuration 26 | # whenever the application is updated. 27 | def config_change(changed, _new, removed) do 28 | MishkaHtmlWeb.Endpoint.config_change(changed, removed) 29 | :ok 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html/plug/not_login_plug.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtml.Plug.NotLoginPlug do 2 | import Plug.Conn 3 | use MishkaHtmlWeb, :controller 4 | alias MishkaUser.Token.CurrentPhoenixToken 5 | alias MishkaHtmlWeb.Router.Helpers, as: Routes 6 | require MishkaTranslator.Gettext 7 | 8 | def init(default), do: default 9 | 10 | def call(conn, _default) do 11 | user_ip = to_string(:inet_parse.ntoa(conn.remote_ip)) 12 | 13 | case CurrentPhoenixToken.verify_token(get_session(conn, :current_token), :current) do 14 | {:ok, :verify_token, :current, current_token_info} -> 15 | on_user_login_failure(conn, user_ip, {:ok, :verify_token, :current, current_token_info}).conn 16 | 17 | conn 18 | |> put_flash( 19 | :error, 20 | MishkaTranslator.Gettext.dgettext("html_auth", "شما از قبل وارد سایت شده اید.") 21 | ) 22 | |> redirect(to: Routes.live_path(conn, MishkaHtmlWeb.HomeLive)) 23 | |> halt() 24 | 25 | _ -> 26 | conn 27 | end 28 | end 29 | 30 | defp on_user_login_failure(conn, user_ip, error) do 31 | state = %MishkaInstaller.Reference.OnUserLoginFailure{ 32 | conn: conn, 33 | ip: user_ip, 34 | endpoint: :html, 35 | error: error 36 | } 37 | 38 | MishkaInstaller.Hook.call(event: "on_user_login_failure", state: state) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", MishkaHtmlWeb.RoomChannel 6 | 7 | # Socket params are passed from the client and can 8 | # be used to verify and authenticate a user. After 9 | # verification, you can put default assigns into 10 | # the socket that will be set for all channels, ie 11 | # 12 | # {:ok, assign(socket, :user_id, verified_user_id)} 13 | # 14 | # To deny connection, return `:error`. 15 | # 16 | # See `Phoenix.Token` documentation for examples in 17 | # performing token verification on connect. 18 | @impl true 19 | def connect(_params, socket, _connect_info) do 20 | {:ok, socket} 21 | end 22 | 23 | # Socket id's are topics that allow you to identify all sockets for a given user: 24 | # 25 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 26 | # 27 | # Would allow you to broadcast a "disconnect" event and terminate 28 | # all active sockets and channels for a given user: 29 | # 30 | # MishkaHtmlWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 31 | # 32 | # Returning `nil` makes this socket anonymous. 33 | @impl true 34 | def id(_socket), do: nil 35 | end 36 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :mishka_html 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_mishka_html_key", 10 | signing_salt: "zKj1Tpml" 11 | ] 12 | 13 | socket "/socket", MishkaHtmlWeb.UserSocket, 14 | websocket: true, 15 | longpoll: false 16 | 17 | socket "/live", Phoenix.LiveView.Socket, 18 | websocket: [connect_info: [:peer_data, session: @session_options]] 19 | 20 | # Serve at "/" the static files from "priv/static" directory. 21 | # 22 | # You should set gzip to true if you are running phx.digest 23 | # when deploying your static files in production. 24 | plug Plug.Static, 25 | at: "/", 26 | from: :mishka_html, 27 | gzip: false, 28 | # only: ~w( css fonts images js favicon.ico robots.txt) 29 | only: ~w(uploads assets fonts images favicon.ico robots.txt css js) 30 | 31 | # Code reloading can be explicitly enabled under the 32 | # :code_reloader configuration of your endpoint. 33 | if code_reloading? do 34 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 35 | plug Phoenix.LiveReloader 36 | plug Phoenix.CodeReloader 37 | end 38 | 39 | plug Phoenix.LiveDashboard.RequestLogger, 40 | param_key: "request_logger", 41 | cookie_key: "request_logger" 42 | 43 | plug Plug.RequestId 44 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 45 | 46 | plug Plug.Parsers, 47 | parsers: [:urlencoded, :multipart, :json], 48 | pass: ["*/*"], 49 | json_decoder: Phoenix.json_library() 50 | 51 | plug Plug.MethodOverride 52 | plug Plug.Head 53 | plug Plug.Session, @session_options 54 | plug MishkaHtmlWeb.Router 55 | end 56 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import MishkaHtmlWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :mishka_html 24 | end 25 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/live/admin_activity_live.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.AdminActivityLive do 2 | use MishkaHtmlWeb, :live_view 3 | 4 | alias MishkaContent.General.Activity 5 | 6 | use MishkaHtml.Helpers.LiveCRUD, 7 | module: MishkaContent.Blog.BlogLink, 8 | redirect: __MODULE__, 9 | router: Routes 10 | 11 | @impl true 12 | def render(assigns) do 13 | Phoenix.View.render(MishkaHtmlWeb.AdminActivityView, "admin_activity_live.html", assigns) 14 | end 15 | 16 | @impl true 17 | def mount(%{"id" => id}, session, socket) do 18 | socket = 19 | case Activity.show_by_id(id) do 20 | {:error, :get_record_by_id, _error_atom} -> 21 | socket 22 | |> put_flash( 23 | :warning, 24 | MishkaTranslator.Gettext.dgettext( 25 | "html_live", 26 | "چنین لاگی وجود ندارد یا ممکن است از قبل حذف شده باشد." 27 | ) 28 | ) 29 | |> push_redirect(to: Routes.live_path(socket, MishkaHtmlWeb.AdminActivitiesLive)) 30 | 31 | {:ok, :get_record_by_id, _error_atom, record} -> 32 | Process.send_after(self(), :menu, 100) 33 | 34 | socket 35 | |> assign( 36 | activity: record, 37 | user_id: Map.get(session, "user_id"), 38 | body_color: "#a29ac3cf" 39 | ) 40 | end 41 | 42 | {:ok, socket} 43 | end 44 | 45 | selected_menue("MishkaHtmlWeb.AdminLogLive") 46 | end 47 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/live/admin_media_manager_live.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.AdminMediaManagerLive do 2 | use MishkaHtmlWeb, :live_view 3 | 4 | use MishkaHtml.Helpers.LiveCRUD, 5 | module: MishkaContent.Blog.BlogLink, 6 | redirect: __MODULE__, 7 | router: Routes 8 | 9 | @impl true 10 | def render(assigns) do 11 | Phoenix.View.render( 12 | MishkaHtmlWeb.AdminMediaManagerView, 13 | "admin_media_manager_live.html", 14 | assigns 15 | ) 16 | end 17 | 18 | @impl true 19 | def mount(_params, session, socket) do 20 | Process.send_after(self(), :menu, 100) 21 | 22 | {:ok, 23 | assign(socket, 24 | user_id: Map.get(session, "user_id"), 25 | page_title: MishkaTranslator.Gettext.dgettext("html_live", "مدیریت فایل ها"), 26 | body_color: "#a29ac3cf" 27 | )} 28 | end 29 | 30 | selected_menue("MishkaHtmlWeb.AdminMediaManagerLive") 31 | end 32 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/live/admin_seo_live.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.AdminSeoLive do 2 | use MishkaHtmlWeb, :live_view 3 | 4 | use MishkaHtml.Helpers.LiveCRUD, 5 | module: MishkaContent.Blog.BlogLink, 6 | redirect: __MODULE__, 7 | router: Routes 8 | 9 | @impl true 10 | def render(assigns) do 11 | Phoenix.View.render(MishkaHtmlWeb.AdminSeoView, "admin_seo_live.html", assigns) 12 | end 13 | 14 | @impl true 15 | def mount(_params, session, socket) do 16 | Process.send_after(self(), :menu, 100) 17 | 18 | {:ok, 19 | assign(socket, 20 | user_id: Map.get(session, "user_id"), 21 | page_title: MishkaTranslator.Gettext.dgettext("html_live", "تنظیمات سئو"), 22 | body_color: "#a29ac3cf" 23 | )} 24 | end 25 | 26 | selected_menue("MishkaHtmlWeb.AdminSeoLive") 27 | end 28 | -------------------------------------------------------------------------------- /apps/mishka_html/lib/mishka_html_web/live/components/admin/dashboard/admin_dashboard_last_users_component.ex: -------------------------------------------------------------------------------- 1 | defmodule MishkaHtmlWeb.Admin.Dashboard.LastUsersComponent do 2 | use MishkaHtmlWeb, :live_component 3 | 4 | def render(assigns) do 5 | ~H""" 6 |
10 | <%= live_flash(@flash, :info) %> 11 |
12 | <% end %> 13 | 14 | <%= if live_flash(@flash, :success) do %> 15 | 16 |17 | <%= live_flash(@flash, :success) %> 18 |
19 | <% end %> 20 | 21 | <%= if live_flash(@flash, :warning) do %> 22 | 23 |24 | <%= live_flash(@flash, :warning) %> 25 |
26 | <% end %> 27 | 28 | <%= if live_flash(@flash, :error) do %> 29 | 30 |31 | <%= live_flash(@flash, :error) %> 32 |
33 | <% end %> 34 |<%= MishkaTranslator.Gettext.dgettext("html_live_component", "ساخته شده به وسیله ترانگل © 2017–2021") %>
9 | 10 |<%= get_flash(@conn, :info) %>
3 |<%= get_flash(@conn, :error) %>
4 | <%= @inner_content %> 5 |